diff --git a/web/src/app/dialogs/startup/components/startup-side-menu.component.html b/web/src/app/dialogs/startup/components/startup-side-menu.component.html new file mode 100644 index 00000000..910490e8 --- /dev/null +++ b/web/src/app/dialogs/startup/components/startup-side-menu.component.html @@ -0,0 +1,62 @@ + + +
+
+
+ +
+
+

Kubernetes History Inspector

+ {{ version() }} +
+

A log viewer for Kubernetes troubleshooting

+
+ +
+
+ +

+ Collect and process cluster logs for visual analysis. +

+
+ +
+ +

Import a previously exported inspection file.

+
+
+ + +
diff --git a/web/src/app/dialogs/startup/components/startup-side-menu.component.scss b/web/src/app/dialogs/startup/components/startup-side-menu.component.scss new file mode 100644 index 00000000..e2efc549 --- /dev/null +++ b/web/src/app/dialogs/startup/components/startup-side-menu.component.scss @@ -0,0 +1,155 @@ +/** + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Color variables based on the design image +$sidebar-bg-color: #f5f6f9; +$primary-blue: #2f54eb; +$text-dark: #1f2329; +$text-muted: #64748b; +$white: #ffffff; +$border-color: #d9d9d9; +$shadow-color: rgba(0, 0, 0, 0.05); +$icon-bg-alpha: rgba(255, 255, 255, 0.2); + +.side-menu-container { + display: grid; + grid-template: + "brand" auto + "actions" 1fr + "footer" auto + / 100%; + gap: 40px; + + width: 220px; + height: 100%; + padding: 20px; + box-sizing: border-box; + + background-color: $sidebar-bg-color; +} + +.brand { + grid-area: brand; + display: grid; + grid-template: + "logo title" auto + "tagline tagline" auto + / auto 1fr; + gap: 16px; + align-items: center; +} + +.logo-container { + grid-area: logo; + display: grid; + place-items: center; + + width: 48px; + height: 48px; + border-radius: 8px; + + background-color: $white; + box-shadow: 0 2px 8px $shadow-color; +} + +.logo { + width: 32px; + height: 32px; +} + +.title-group { + grid-area: title; + display: grid; + grid-template: + "name" auto + "version" auto + / 100%; + gap: 4px; +} + +.title { + margin: 0; + + font-family: "Inter", sans-serif; + font-size: 18px; + font-weight: 700; + color: $text-dark; +} + +.tagline { + grid-area: tagline; + margin: 0; + + font-size: 12px; + font-style: italic; + color: $text-muted; + line-height: 1.4; +} + +.version { + font-size: 12px; + color: $text-muted; +} + +.actions { + grid-area: actions; + display: grid; + gap: 12px; + align-content: space-around; +} + +.action-item { + display: flex; + flex-direction: column; + gap: 8px; +} + +.description { + margin: 0; + + font-size: 12px; + color: $text-muted; + line-height: 1.5; +} + +.footer-links { + grid-area: footer; + display: grid; + gap: 12px; +} + +.footer-link { + display: grid; + grid-template: "icon text" / auto 1fr; + gap: 8px; + align-items: center; + + font-size: 14px; + color: $text-muted; + text-decoration: none; + + transition: color 0.2s; + + &:hover { + color: $primary-blue; + } + + mat-icon { + width: 18px; + height: 18px; + font-size: 18px; + } +} diff --git a/web/src/app/dialogs/startup/components/startup-side-menu.component.spec.ts b/web/src/app/dialogs/startup/components/startup-side-menu.component.spec.ts new file mode 100644 index 00000000..245200a0 --- /dev/null +++ b/web/src/app/dialogs/startup/components/startup-side-menu.component.spec.ts @@ -0,0 +1,133 @@ +/** + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { StartupSideMenuComponent } from './startup-side-menu.component'; +import { By } from '@angular/platform-browser'; + +describe('StartupSideMenuComponent', () => { + let component: StartupSideMenuComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [StartupSideMenuComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(StartupSideMenuComponent); + component = fixture.componentInstance; + + // Set required inputs + fixture.componentRef.setInput('version', 'V1.0.0'); + fixture.componentRef.setInput('links', [ + { + icon: 'description', + label: 'Documentation', + url: 'https://github.com/GoogleCloudPlatform/kubernetes-history-inspector#doc', + }, + { + icon: 'bug_report', + label: 'Report Bug', + url: 'https://github.com/GoogleCloudPlatform/kubernetes-history-inspector/issues', + }, + { + icon: 'code', + label: 'GitHub', + url: 'https://github.com/GoogleCloudPlatform/kubernetes-history-inspector#github', + }, + ]); + + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should display the version', () => { + const versionEl = fixture.debugElement.query( + By.css('.version'), + ).nativeElement; + expect(versionEl.textContent).toContain('V1.0.0'); + }); + + it('should emit newInvestigation when New Inspection button is clicked', () => { + const emitSpy = spyOn(component.newInvestigation, 'emit'); + + // Find the button with text "New Inspection" + const buttons = fixture.debugElement.queryAll( + By.css('button[mat-flat-button]'), + ); + const btn = buttons.find((b) => + b.nativeElement.textContent.includes('New Inspection'), + ); + + expect(btn).toBeTruthy(); + btn!.nativeElement.click(); + + expect(emitSpy).toHaveBeenCalled(); + }); + + it('should emit openKhiFile when Open .khi file button is clicked', () => { + const emitSpy = spyOn(component.openKhiFile, 'emit'); + + // Find the button with text "Open .khi file" + const buttons = fixture.debugElement.queryAll( + By.css('button[mat-stroked-button]'), + ); + const btn = buttons.find((b) => + b.nativeElement.textContent.includes('Open .khi file'), + ); + + expect(btn).toBeTruthy(); + btn!.nativeElement.click(); + + expect(emitSpy).toHaveBeenCalled(); + }); + + it('should have correct external links', () => { + const links = fixture.debugElement.queryAll(By.css('.footer-link')); + expect(links.length).toBe(3); + + const docLink = links.find((l) => + l.nativeElement.textContent.includes('Documentation'), + ); + expect(docLink).toBeTruthy(); + expect(docLink!.nativeElement.getAttribute('href')).toBe( + 'https://github.com/GoogleCloudPlatform/kubernetes-history-inspector#doc', + ); + expect(docLink!.nativeElement.getAttribute('target')).toBe('_blank'); + expect(docLink!.nativeElement.getAttribute('rel')).toBe( + 'noopener noreferrer', + ); + + const bugLink = links.find((l) => + l.nativeElement.textContent.includes('Report Bug'), + ); + expect(bugLink).toBeTruthy(); + expect(bugLink!.nativeElement.getAttribute('href')).toBe( + 'https://github.com/GoogleCloudPlatform/kubernetes-history-inspector/issues', + ); + + const githubLink = links.find((l) => + l.nativeElement.textContent.includes('GitHub'), + ); + expect(githubLink).toBeTruthy(); + expect(githubLink!.nativeElement.getAttribute('href')).toBe( + 'https://github.com/GoogleCloudPlatform/kubernetes-history-inspector#github', + ); + }); +}); diff --git a/web/src/app/dialogs/startup/components/startup-side-menu.component.ts b/web/src/app/dialogs/startup/components/startup-side-menu.component.ts new file mode 100644 index 00000000..9a0b6e99 --- /dev/null +++ b/web/src/app/dialogs/startup/components/startup-side-menu.component.ts @@ -0,0 +1,51 @@ +/** + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, input, output } from '@angular/core'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { KHIIconRegistrationModule } from 'src/app/shared/module/icon-registration.module'; +import { SidebarLink } from '../types/startup-side-menu.types'; + +/** + * Sidebar component for the startup dialog. + * Displays the application logo, title, main action buttons, and footer links. + */ +@Component({ + selector: 'khi-startup-side-menu', + imports: [ + MatTooltipModule, + MatIconModule, + MatButtonModule, + KHIIconRegistrationModule, + ], + templateUrl: './startup-side-menu.component.html', + styleUrls: ['./startup-side-menu.component.scss'], +}) +export class StartupSideMenuComponent { + /** The current version of the application to be displayed. */ + public readonly version = input.required(); + + /** The list of links to be displayed in the footer. */ + public readonly links = input.required(); + + /** Emitted when the user clicks the 'New Investigation' button. */ + public readonly newInvestigation = output(); + + /** Emitted when the user clicks the 'Open .khi file' button. */ + public readonly openKhiFile = output(); +} diff --git a/web/src/app/dialogs/startup/components/startup-side-menu.stories.ts b/web/src/app/dialogs/startup/components/startup-side-menu.stories.ts new file mode 100644 index 00000000..74337c69 --- /dev/null +++ b/web/src/app/dialogs/startup/components/startup-side-menu.stories.ts @@ -0,0 +1,54 @@ +/** + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Meta, StoryObj } from '@storybook/angular'; +import { StartupSideMenuComponent } from './startup-side-menu.component'; + +const meta: Meta = { + title: 'Dialogs/Startup/StartupSideMenu', + component: StartupSideMenuComponent, + tags: ['autodocs'], + argTypes: { + newInvestigation: { action: 'newInvestigation' }, + openKhiFile: { action: 'openKhiFile' }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + version: 'v0.100.21', + links: [ + { + icon: 'description', + label: 'Documentation', + url: 'https://github.com/GoogleCloudPlatform/kubernetes-history-inspector', + }, + { + icon: 'bug_report', + label: 'Report Bug', + url: 'https://github.com/GoogleCloudPlatform/kubernetes-history-inspector/issues', + }, + { + icon: 'code', + label: 'GitHub', + url: 'https://github.com/GoogleCloudPlatform/kubernetes-history-inspector', + }, + ], + }, +}; diff --git a/web/src/app/dialogs/startup/types/startup-side-menu.types.ts b/web/src/app/dialogs/startup/types/startup-side-menu.types.ts new file mode 100644 index 00000000..f6a4256c --- /dev/null +++ b/web/src/app/dialogs/startup/types/startup-side-menu.types.ts @@ -0,0 +1,27 @@ +/** + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Represents a link to be displayed in the sidebar footer. + */ +export interface SidebarLink { + /** The name of the Material icon to display. */ + icon: string; + /** The label text to display. */ + label: string; + /** The URL destination of the link. */ + url: string; +}