diff --git a/backend/capellacollab/navbar/routes.py b/backend/capellacollab/navbar/routes.py new file mode 100644 index 000000000..f6849d9ad --- /dev/null +++ b/backend/capellacollab/navbar/routes.py @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +import fastapi +from sqlalchemy import orm + +from capellacollab.core import database +from capellacollab.settings.configuration import core as config_core +from capellacollab.settings.configuration import ( + models as settings_config_models, +) +from capellacollab.settings.configuration.models import NavbarConfiguration + +router = fastapi.APIRouter() + + +@router.get( + "/navbar", + response_model=NavbarConfiguration, +) +def get_navbar(db: orm.Session = fastapi.Depends(database.get_db)): + cfg = config_core.get_config(db, "global") + assert isinstance(cfg, settings_config_models.GlobalConfiguration) + + return NavbarConfiguration.model_validate(cfg.navbar.model_dump()) diff --git a/backend/capellacollab/routes.py b/backend/capellacollab/routes.py index 067d939f8..656e7894f 100644 --- a/backend/capellacollab/routes.py +++ b/backend/capellacollab/routes.py @@ -11,6 +11,7 @@ from capellacollab.events import routes as events_router from capellacollab.health import routes as health_routes from capellacollab.metadata import routes as core_metadata +from capellacollab.navbar import routes as navbar_routes from capellacollab.notices import routes as notices_routes from capellacollab.projects import routes as projects_routes from capellacollab.sessions import routes as sessions_routes @@ -29,6 +30,7 @@ tags=["Health"], ) router.include_router(core_metadata.router, tags=["Metadata"]) +router.include_router(navbar_routes.router, tags=["Navbar"]) router.include_router( sessions_routes.router, prefix="/sessions", diff --git a/backend/capellacollab/settings/configuration/models.py b/backend/capellacollab/settings/configuration/models.py index 5404af966..36fc6038c 100644 --- a/backend/capellacollab/settings/configuration/models.py +++ b/backend/capellacollab/settings/configuration/models.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 import abc +import enum import typing as t import pydantic @@ -9,6 +10,7 @@ from capellacollab.core import database from capellacollab.core import pydantic as core_pydantic +from capellacollab.users import models as users_models class DatabaseConfiguration(database.Base): @@ -37,6 +39,56 @@ class MetadataConfiguration(core_pydantic.BaseModelStrict): environment: str = pydantic.Field(default="-", description="general") +class BuiltInLinkItem(str, enum.Enum): + GRAFANA = "grafana" + PROMETHEUS = "prometheus" + DOCUMENTATION = "documentation" + + +class NavbarLink(core_pydantic.BaseModelStrict): + name: str + role: users_models.Role = pydantic.Field( + description="Role required to see this link.", + ) + + +class BuiltInNavbarLink(NavbarLink): + service: BuiltInLinkItem = pydantic.Field( + description="Built-in service to link to.", + ) + + +class CustomNavbarLink(NavbarLink): + href: str = pydantic.Field( + description="URL to link to.", + ) + + +class NavbarConfiguration(core_pydantic.BaseModelStrict): + external_links: list[BuiltInNavbarLink | CustomNavbarLink] = ( + pydantic.Field( + default=[ + BuiltInNavbarLink( + name="Grafana", + service=BuiltInLinkItem.GRAFANA, + role=users_models.Role.ADMIN, + ), + BuiltInNavbarLink( + name="Prometheus", + service=BuiltInLinkItem.PROMETHEUS, + role=users_models.Role.ADMIN, + ), + BuiltInNavbarLink( + name="Documentation", + service=BuiltInLinkItem.DOCUMENTATION, + role=users_models.Role.USER, + ), + ], + description="Links to display in the navigation bar.", + ) + ) + + class ConfigurationBase(core_pydantic.BaseModelStrict, abc.ABC): """ Base class for configuration models. Can be used to define new configurations @@ -55,6 +107,10 @@ class GlobalConfiguration(ConfigurationBase): default_factory=MetadataConfiguration ) + navbar: NavbarConfiguration = pydantic.Field( + default_factory=NavbarConfiguration + ) + # All subclasses of ConfigurationBase are automatically registered using this dict. NAME_TO_MODEL_TYPE_MAPPING: dict[str, t.Type[ConfigurationBase]] = { diff --git a/backend/capellacollab/users/models.py b/backend/capellacollab/users/models.py index 0aa250916..1ee1ea146 100644 --- a/backend/capellacollab/users/models.py +++ b/backend/capellacollab/users/models.py @@ -20,7 +20,7 @@ from capellacollab.users.tokens.models import DatabaseUserToken -class Role(enum.Enum): +class Role(str, enum.Enum): USER = "user" ADMIN = "administrator" diff --git a/backend/tests/settings/test_global_configuration.py b/backend/tests/settings/test_global_configuration.py index 9a0655dd0..03437295d 100644 --- a/backend/tests/settings/test_global_configuration.py +++ b/backend/tests/settings/test_global_configuration.py @@ -149,3 +149,47 @@ def get_mock_own_user(): response = client.get("/api/v1/metadata") assert response.status_code == 200 assert response.json()["environment"] == "test" + + +def test_navbar_is_updated( + client: testclient.TestClient, + db: orm.Session, + executor_name: str, +): + admin = users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) + + def get_mock_own_user(): + return admin + + app.dependency_overrides[users_injectables.get_own_user] = ( + get_mock_own_user + ) + + response = client.put( + "/api/v1/settings/configurations/global", + json={ + "navbar": { + "external_links": [ + { + "name": "Example", + "href": "https://example.com", + "role": "user", + } + ] + } + }, + ) + + assert response.status_code == 200 + + del app.dependency_overrides[users_injectables.get_own_user] + + response = client.get("/api/v1/navbar") + assert response.status_code == 200 + assert response.json()["external_links"][0] == { + "name": "Example", + "href": "https://example.com", + "role": "user", + } diff --git a/docs/docs/admin/configure-for-your-org.md b/docs/docs/admin/configure-for-your-org.md new file mode 100644 index 000000000..165898bc8 --- /dev/null +++ b/docs/docs/admin/configure-for-your-org.md @@ -0,0 +1,53 @@ +<!-- + ~ SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + ~ SPDX-License-Identifier: Apache-2.0 + --> + +# Configure for your Organization + +When running the Collaboration Manager in production, you may want to provide +information about the team responsible for it, as well as an imprint and +privacy policy. + +You can set this information from the configuration page in the admin +interface. Navigate to _Settings_, then _Configuration_, then edit the file to +your liking. + +Here, you can also edit the links in the navigation bar if you are not using +the default monitoring services. + +```yaml +metadata: + privacy_policy_url: https://example.com/privacy + imprint_url: https://example.com/imprint + provider: Systems Engineering Toolchain team + authentication_provider: OAuth2 + environment: '-' +navbar: + external_links: + - name: Grafana + service: grafana + role: administrator + - name: Prometheus + service: prometheus + role: administrator + - name: Documentation + service: documentation + role: user +``` + +In addition to the default service links, you can add your own by using `href` +instead of `service`. + +```yaml +navbar: + external_links: + - name: Example + href: https://example.com + role: user +``` + +The `role` field and can be one of `user` or `administrator`. While this will +hide the link from users without the appropriate role, it is not a security +feature, and you should make sure that the linked service enforces the +necessary access controls. diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 2c82a132c..863559e3b 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -102,6 +102,7 @@ nav: - Image builder: admin/ci-templates/gitlab/image-builder.md - Kubernetes deployment: admin/ci-templates/gitlab/k8s-deploy.md - Command line tool: admin/cli.md + - Configure for your Organization: admin/configure-for-your-org.md - Troubleshooting: admin/troubleshooting.md - Developer Documentation: - Introduction: development/index.md diff --git a/frontend/src/app/general/header/header.component.html b/frontend/src/app/general/header/header.component.html index 629e833d9..e5615555d 100644 --- a/frontend/src/app/general/header/header.component.html +++ b/frontend/src/app/general/header/header.component.html @@ -20,35 +20,38 @@ <div class="ml-5 basis-1/3 select-none text-2xl text-primary"> Capella Collaboration Manager </div> - <div class="flex basis-1/3 justify-center gap-2"> - @for (item of navBarService.navBarItems; track item.name) { - @if (userService.validateUserRole(item.requiredRole)) { - @if (item.href) { - <a - mat-flat-button - color="primary" - [attr.href]="item.href" - [attr.target]="item.target" - class="" - > - {{ item.name }} - @if (item.icon) { - <mat-icon iconPositionEnd>{{ item.icon }}</mat-icon> - } - </a> - } @else { - <a - mat-flat-button - color="primary" - [routerLink]="item.routerLink" - class="" - > - {{ item.name }} - </a> + @if (navBarService.navbarItems$ | async) { + <div class="flex basis-1/3 justify-center gap-2"> + @for (item of navBarService.navbarItems$ | async; track item.name) { + @if (userService.validateUserRole(item.requiredRole)) { + @if (item.href) { + <a + mat-flat-button + color="primary" + [attr.href]="item.href" + [attr.target]="item.target" + class="" + > + {{ item.name }} + @if (item.icon) { + <mat-icon iconPositionEnd>{{ item.icon }}</mat-icon> + } + </a> + } @else { + <a + mat-flat-button + color="primary" + [routerLink]="item.routerLink" + class="" + > + {{ item.name }} + </a> + } } } - } - </div> + </div> + } + <div class="!mr-5 hidden basis-1/3 items-center justify-end gap-2 xl:flex"> <mat-menu #profileMenu="matMenu" class="flex items-center"> <a diff --git a/frontend/src/app/general/header/header.component.ts b/frontend/src/app/general/header/header.component.ts index d6cbea9a4..a27fdaada 100644 --- a/frontend/src/app/general/header/header.component.ts +++ b/frontend/src/app/general/header/header.component.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { AsyncPipe } from '@angular/common'; import { Component } from '@angular/core'; import { MatIconButton, MatAnchor, MatButton } from '@angular/material/button'; import { MatIcon } from '@angular/material/icon'; @@ -27,6 +28,7 @@ import { BreadcrumbsComponent } from '../breadcrumbs/breadcrumbs.component'; MatButton, MatMenuTrigger, BreadcrumbsComponent, + AsyncPipe, ], }) export class HeaderComponent { diff --git a/frontend/src/app/general/header/header.stories.ts b/frontend/src/app/general/header/header.stories.ts index a065ce329..6959fad4f 100644 --- a/frontend/src/app/general/header/header.stories.ts +++ b/frontend/src/app/general/header/header.stories.ts @@ -4,8 +4,10 @@ */ import { Meta, moduleMetadata, StoryObj } from '@storybook/angular'; +import { of } from 'rxjs'; import { UserWrapperService } from 'src/app/services/user/user.service'; import { mockUser, MockUserService } from 'src/storybook/user'; +import { NavBarItem, NavBarService } from '../nav-bar/nav-bar.service'; import { HeaderComponent } from './header.component'; const meta: Meta<HeaderComponent> = { @@ -16,6 +18,45 @@ const meta: Meta<HeaderComponent> = { export default meta; type Story = StoryObj<HeaderComponent>; +class MockNavbarService implements Partial<NavBarService> { + readonly navbarItems$ = of<NavBarItem[]>([ + { + name: 'Projects', + requiredRole: 'user', + }, + { + name: 'Sessions', + requiredRole: 'user', + }, + { + name: 'Session viewer', + requiredRole: 'user', + }, + { + name: 'Session overview', + requiredRole: 'administrator', + }, + { + name: 'Prometheus', + requiredRole: 'administrator', + icon: 'open_in_new', + href: '#', + }, + { + name: 'Grafana', + requiredRole: 'administrator', + icon: 'open_in_new', + href: '#', + }, + { + name: 'Documentation', + requiredRole: 'user', + icon: 'open_in_new', + href: '#', + }, + ]); +} + export const NormalUser: Story = { args: {}, decorators: [ @@ -25,6 +66,10 @@ export const NormalUser: Story = { provide: UserWrapperService, useFactory: () => new MockUserService(mockUser), }, + { + provide: NavBarService, + useFactory: () => new MockNavbarService(), + }, ], }), ], @@ -43,6 +88,10 @@ export const Administrator: Story = { useFactory: () => new MockUserService({ ...mockUser, role: 'administrator' }), }, + { + provide: NavBarService, + useFactory: () => new MockNavbarService(), + }, ], }), ], diff --git a/frontend/src/app/general/nav-bar-menu/nav-bar-menu.component.html b/frontend/src/app/general/nav-bar-menu/nav-bar-menu.component.html index e1edcb466..d662722bd 100644 --- a/frontend/src/app/general/nav-bar-menu/nav-bar-menu.component.html +++ b/frontend/src/app/general/nav-bar-menu/nav-bar-menu.component.html @@ -4,7 +4,7 @@ --> <mat-list> - @for (item of navBarService.navBarItems; track $index) { + @for (item of navBarService.navbarItems$ | async; track $index) { <div> @if (userService.validateUserRole(item.requiredRole)) { @if (item.href) { diff --git a/frontend/src/app/general/nav-bar-menu/nav-bar-menu.component.ts b/frontend/src/app/general/nav-bar-menu/nav-bar-menu.component.ts index 45b0f695f..11afa225c 100644 --- a/frontend/src/app/general/nav-bar-menu/nav-bar-menu.component.ts +++ b/frontend/src/app/general/nav-bar-menu/nav-bar-menu.component.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { AsyncPipe } from '@angular/common'; import { Component } from '@angular/core'; import { MatDivider } from '@angular/material/divider'; import { MatIcon } from '@angular/material/icon'; @@ -17,7 +18,7 @@ import { UserWrapperService } from 'src/app/services/user/user.service'; templateUrl: './nav-bar-menu.component.html', styleUrls: ['./nav-bar-menu.component.css'], standalone: true, - imports: [MatList, MatListItem, MatIcon, RouterLink, MatDivider], + imports: [MatList, MatListItem, MatIcon, RouterLink, MatDivider, AsyncPipe], }) export class NavBarMenuComponent { constructor( diff --git a/frontend/src/app/general/nav-bar/nav-bar.service.ts b/frontend/src/app/general/nav-bar/nav-bar.service.ts index 4d98547fb..4b3bbebee 100644 --- a/frontend/src/app/general/nav-bar/nav-bar.service.ts +++ b/frontend/src/app/general/nav-bar/nav-bar.service.ts @@ -5,20 +5,53 @@ import { Injectable } from '@angular/core'; import { MatSidenav } from '@angular/material/sidenav'; -import { UserRole } from 'src/app/services/user/user.service'; +import { BehaviorSubject, map, Observable, tap } from 'rxjs'; +import { + BuiltInLinkItem, + NavbarConfigurationOutput, + NavbarService as OpenAPINavbarService, + Role, +} from 'src/app/openapi'; import { environment } from 'src/environments/environment'; @Injectable({ providedIn: 'root', }) export class NavBarService { - sidenav?: MatSidenav; + constructor(private navbarService: OpenAPINavbarService) { + this.loadNavbarConfig().subscribe(); + } + + loadNavbarConfig(): Observable<NavbarConfigurationOutput> { + return this.navbarService + .getNavbar() + .pipe(tap((navConf) => this._navbarConfig.next(navConf))); + } + + private _navbarConfig = new BehaviorSubject< + NavbarConfigurationOutput | undefined + >(undefined); + readonly navbarItems$ = this._navbarConfig.pipe( + map( + (navbarConfig): NavBarItem[] => + navbarConfig?.external_links.map((link) => ({ + name: link.name, + href: link.service ? this._linkMap[link.service] : link.href, + target: '_blank', + icon: 'open_in_new', + requiredRole: link.role, + })) || [], + ), + map((items) => [...this.internalNavbarItems, ...items]), + ); + + sidenav?: MatSidenav; toggle(): void { this.sidenav?.toggle(); } - navBarItems: NavBarItem[] = [ + private internalNavbarItems: NavBarItem[] = [ { name: 'Projects', routerLink: '/projects', @@ -39,28 +72,13 @@ export class NavBarService { routerLink: ['/sessions', 'overview'], requiredRole: 'administrator', }, - { - name: 'Prometheus', - href: environment.prometheus_url, - target: '_blank', - icon: 'open_in_new', - requiredRole: 'administrator', - }, - { - name: 'Grafana', - href: environment.grafana_url, - target: '_blank', - icon: 'open_in_new', - requiredRole: 'administrator', - }, - { - name: 'Documentation', - href: environment.docs_url + '/', - target: '_blank', - icon: 'open_in_new', - requiredRole: 'user', - }, ]; + + private _linkMap: Record<BuiltInLinkItem, string> = { + prometheus: environment.prometheus_url, + grafana: environment.grafana_url, + documentation: environment.docs_url + '/', + }; } export type NavBarItem = { @@ -68,6 +86,6 @@ export type NavBarItem = { routerLink?: string | string[]; href?: string; target?: string; - requiredRole: UserRole; + requiredRole: Role; icon?: string; }; diff --git a/frontend/src/app/openapi/.openapi-generator/FILES b/frontend/src/app/openapi/.openapi-generator/FILES index 0a149ccfc..d5da3d37b 100644 --- a/frontend/src/app/openapi/.openapi-generator/FILES +++ b/frontend/src/app/openapi/.openapi-generator/FILES @@ -6,6 +6,7 @@ api/events.service.ts api/health.service.ts api/integrations-pure-variants.service.ts api/metadata.service.ts +api/navbar.service.ts api/notices.service.ts api/projects-events.service.ts api/projects-models-backups.service.ts @@ -31,6 +32,8 @@ model/authorization-response.ts model/backup-pipeline-run.ts model/backup.ts model/base-user.ts +model/built-in-link-item.ts +model/built-in-navbar-link.ts model/cpu-resources-input.ts model/cpu-resources-output.ts model/create-backup.ts @@ -43,6 +46,7 @@ model/create-tool-nature-output.ts model/create-tool-output.ts model/create-tool-version-input.ts model/create-tool-version-output.ts +model/custom-navbar-link.ts model/diagram-cache-metadata.ts model/diagram-metadata.ts model/environment.ts @@ -76,6 +80,9 @@ model/metadata-configuration-output.ts model/metadata.ts model/model-artifact-status.ts model/models.ts +model/navbar-configuration-input-external-links-inner.ts +model/navbar-configuration-input.ts +model/navbar-configuration-output.ts model/notice-level.ts model/notice-response.ts model/page-history-event.ts diff --git a/frontend/src/app/openapi/api/api.ts b/frontend/src/app/openapi/api/api.ts index 64dcdb524..f498c004f 100644 --- a/frontend/src/app/openapi/api/api.ts +++ b/frontend/src/app/openapi/api/api.ts @@ -21,6 +21,8 @@ export * from './integrations-pure-variants.service'; import { IntegrationsPureVariantsService } from './integrations-pure-variants.service'; export * from './metadata.service'; import { MetadataService } from './metadata.service'; +export * from './navbar.service'; +import { NavbarService } from './navbar.service'; export * from './notices.service'; import { NoticesService } from './notices.service'; export * from './projects.service'; @@ -57,4 +59,4 @@ export * from './users-token.service'; import { UsersTokenService } from './users-token.service'; export * from './users-workspaces.service'; import { UsersWorkspacesService } from './users-workspaces.service'; -export const APIS = [AuthenticationService, ConfigurationService, EventsService, HealthService, IntegrationsPureVariantsService, MetadataService, NoticesService, ProjectsService, ProjectsEventsService, ProjectsModelsService, ProjectsModelsBackupsService, ProjectsModelsDiagramsService, ProjectsModelsGitService, ProjectsModelsModelComplexityBadgeService, ProjectsModelsRestrictionsService, ProjectsModelsT4CService, SessionsService, SettingsModelsourcesGitService, SettingsModelsourcesT4CService, ToolsService, UsersService, UsersSessionsService, UsersTokenService, UsersWorkspacesService]; +export const APIS = [AuthenticationService, ConfigurationService, EventsService, HealthService, IntegrationsPureVariantsService, MetadataService, NavbarService, NoticesService, ProjectsService, ProjectsEventsService, ProjectsModelsService, ProjectsModelsBackupsService, ProjectsModelsDiagramsService, ProjectsModelsGitService, ProjectsModelsModelComplexityBadgeService, ProjectsModelsRestrictionsService, ProjectsModelsT4CService, SessionsService, SettingsModelsourcesGitService, SettingsModelsourcesT4CService, ToolsService, UsersService, UsersSessionsService, UsersTokenService, UsersWorkspacesService]; diff --git a/frontend/src/app/openapi/api/navbar.service.ts b/frontend/src/app/openapi/api/navbar.service.ts new file mode 100644 index 000000000..ec60ae190 --- /dev/null +++ b/frontend/src/app/openapi/api/navbar.service.ts @@ -0,0 +1,155 @@ +/* + * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + * SPDX-License-Identifier: Apache-2.0 + * + * Capella Collaboration + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + + To generate a new version, run `make openapi` in the root directory of this repository. + */ + +/* tslint:disable:no-unused-variable member-ordering */ + +import { Inject, Injectable, Optional } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams, + HttpResponse, HttpEvent, HttpParameterCodec, HttpContext + } from '@angular/common/http'; +import { CustomHttpParameterCodec } from '../encoder'; +import { Observable } from 'rxjs'; + +// @ts-ignore +import { NavbarConfigurationOutput } from '../model/navbar-configuration-output'; + +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; +import { Configuration } from '../configuration'; + + + +@Injectable({ + providedIn: 'root' +}) +export class NavbarService { + + protected basePath = 'http://localhost'; + public defaultHeaders = new HttpHeaders(); + public configuration = new Configuration(); + public encoder: HttpParameterCodec; + + constructor(protected httpClient: HttpClient, @Optional()@Inject(BASE_PATH) basePath: string|string[], @Optional() configuration: Configuration) { + if (configuration) { + this.configuration = configuration; + } + if (typeof this.configuration.basePath !== 'string') { + const firstBasePath = Array.isArray(basePath) ? basePath[0] : undefined; + if (firstBasePath != undefined) { + basePath = firstBasePath; + } + + if (typeof basePath !== 'string') { + basePath = this.basePath; + } + this.configuration.basePath = basePath; + } + this.encoder = this.configuration.encoder || new CustomHttpParameterCodec(); + } + + + // @ts-ignore + private addToHttpParams(httpParams: HttpParams, value: any, key?: string): HttpParams { + if (typeof value === "object" && value instanceof Date === false) { + httpParams = this.addToHttpParamsRecursive(httpParams, value); + } else { + httpParams = this.addToHttpParamsRecursive(httpParams, value, key); + } + return httpParams; + } + + private addToHttpParamsRecursive(httpParams: HttpParams, value?: any, key?: string): HttpParams { + if (value == null) { + return httpParams; + } + + if (typeof value === "object") { + if (Array.isArray(value)) { + (value as any[]).forEach( elem => httpParams = this.addToHttpParamsRecursive(httpParams, elem, key)); + } else if (value instanceof Date) { + if (key != null) { + httpParams = httpParams.append(key, (value as Date).toISOString().substring(0, 10)); + } else { + throw Error("key may not be null if value is Date"); + } + } else { + Object.keys(value).forEach( k => httpParams = this.addToHttpParamsRecursive( + httpParams, value[k], key != null ? `${key}.${k}` : k)); + } + } else if (key != null) { + httpParams = httpParams.append(key, value); + } else { + throw Error("key may not be null if value is not object or array"); + } + return httpParams; + } + + /** + * Get Navbar + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public getNavbar(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<NavbarConfigurationOutput>; + public getNavbar(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<NavbarConfigurationOutput>>; + public getNavbar(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<NavbarConfigurationOutput>>; + public getNavbar(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<any> { + + let localVarHeaders = this.defaultHeaders; + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + let localVarTransferCache: boolean | undefined = options && options.transferCache; + if (localVarTransferCache === undefined) { + localVarTransferCache = true; + } + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/v1/navbar`; + return this.httpClient.request<NavbarConfigurationOutput>('get', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: <any>responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + transferCache: localVarTransferCache, + reportProgress: reportProgress + } + ); + } + +} diff --git a/frontend/src/app/openapi/model/built-in-link-item.ts b/frontend/src/app/openapi/model/built-in-link-item.ts new file mode 100644 index 000000000..d122038a7 --- /dev/null +++ b/frontend/src/app/openapi/model/built-in-link-item.ts @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + * SPDX-License-Identifier: Apache-2.0 + * + * Capella Collaboration + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + + To generate a new version, run `make openapi` in the root directory of this repository. + */ + + + +export type BuiltInLinkItem = 'grafana' | 'prometheus' | 'documentation'; + +export const BuiltInLinkItem = { + Grafana: 'grafana' as BuiltInLinkItem, + Prometheus: 'prometheus' as BuiltInLinkItem, + Documentation: 'documentation' as BuiltInLinkItem +}; + diff --git a/frontend/src/app/openapi/model/built-in-navbar-link.ts b/frontend/src/app/openapi/model/built-in-navbar-link.ts new file mode 100644 index 000000000..307119bcb --- /dev/null +++ b/frontend/src/app/openapi/model/built-in-navbar-link.ts @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + * SPDX-License-Identifier: Apache-2.0 + * + * Capella Collaboration + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + + To generate a new version, run `make openapi` in the root directory of this repository. + */ + +import { Role } from './role'; +import { BuiltInLinkItem } from './built-in-link-item'; + + +export interface BuiltInNavbarLink { + name: string; + /** + * Role required to see this link. + */ + role: Role; + /** + * Built-in service to link to. + */ + service: BuiltInLinkItem; +} +export namespace BuiltInNavbarLink { +} + + diff --git a/frontend/src/app/openapi/model/custom-navbar-link.ts b/frontend/src/app/openapi/model/custom-navbar-link.ts new file mode 100644 index 000000000..d74966ec2 --- /dev/null +++ b/frontend/src/app/openapi/model/custom-navbar-link.ts @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + * SPDX-License-Identifier: Apache-2.0 + * + * Capella Collaboration + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + + To generate a new version, run `make openapi` in the root directory of this repository. + */ + +import { Role } from './role'; + + +export interface CustomNavbarLink { + name: string; + /** + * Role required to see this link. + */ + role: Role; + /** + * URL to link to. + */ + href: string; +} +export namespace CustomNavbarLink { +} + + diff --git a/frontend/src/app/openapi/model/global-configuration-input.ts b/frontend/src/app/openapi/model/global-configuration-input.ts index 6db405a4d..70c4954a1 100644 --- a/frontend/src/app/openapi/model/global-configuration-input.ts +++ b/frontend/src/app/openapi/model/global-configuration-input.ts @@ -9,6 +9,7 @@ + To generate a new version, run `make openapi` in the root directory of this repository. */ +import { NavbarConfigurationInput } from './navbar-configuration-input'; import { MetadataConfigurationInput } from './metadata-configuration-input'; @@ -17,5 +18,6 @@ import { MetadataConfigurationInput } from './metadata-configuration-input'; */ export interface GlobalConfigurationInput { metadata?: MetadataConfigurationInput; + navbar?: NavbarConfigurationInput; } diff --git a/frontend/src/app/openapi/model/global-configuration-output.ts b/frontend/src/app/openapi/model/global-configuration-output.ts index f501c7dc7..cab85de70 100644 --- a/frontend/src/app/openapi/model/global-configuration-output.ts +++ b/frontend/src/app/openapi/model/global-configuration-output.ts @@ -9,6 +9,7 @@ + To generate a new version, run `make openapi` in the root directory of this repository. */ +import { NavbarConfigurationOutput } from './navbar-configuration-output'; import { MetadataConfigurationOutput } from './metadata-configuration-output'; @@ -17,5 +18,6 @@ import { MetadataConfigurationOutput } from './metadata-configuration-output'; */ export interface GlobalConfigurationOutput { metadata: MetadataConfigurationOutput; + navbar: NavbarConfigurationOutput; } diff --git a/frontend/src/app/openapi/model/models.ts b/frontend/src/app/openapi/model/models.ts index fbf5f0361..a7d469e27 100644 --- a/frontend/src/app/openapi/model/models.ts +++ b/frontend/src/app/openapi/model/models.ts @@ -13,6 +13,8 @@ export * from './authorization-response'; export * from './backup'; export * from './backup-pipeline-run'; export * from './base-user'; +export * from './built-in-link-item'; +export * from './built-in-navbar-link'; export * from './cpu-resources-input'; export * from './cpu-resources-output'; export * from './create-backup'; @@ -25,6 +27,7 @@ export * from './create-tool-nature-output'; export * from './create-tool-output'; export * from './create-tool-version-input'; export * from './create-tool-version-output'; +export * from './custom-navbar-link'; export * from './diagram-cache-metadata'; export * from './diagram-metadata'; export * from './environment'; @@ -57,6 +60,9 @@ export * from './metadata'; export * from './metadata-configuration-input'; export * from './metadata-configuration-output'; export * from './model-artifact-status'; +export * from './navbar-configuration-input'; +export * from './navbar-configuration-input-external-links-inner'; +export * from './navbar-configuration-output'; export * from './notice-level'; export * from './notice-response'; export * from './page-history-event'; diff --git a/frontend/src/app/openapi/model/navbar-configuration-input-external-links-inner.ts b/frontend/src/app/openapi/model/navbar-configuration-input-external-links-inner.ts new file mode 100644 index 000000000..a25552cc0 --- /dev/null +++ b/frontend/src/app/openapi/model/navbar-configuration-input-external-links-inner.ts @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + * SPDX-License-Identifier: Apache-2.0 + * + * Capella Collaboration + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + + To generate a new version, run `make openapi` in the root directory of this repository. + */ + +import { BuiltInNavbarLink } from './built-in-navbar-link'; +import { Role } from './role'; +import { CustomNavbarLink } from './custom-navbar-link'; +import { BuiltInLinkItem } from './built-in-link-item'; + + +export interface NavbarConfigurationInputExternalLinksInner { + name: string; + /** + * Role required to see this link. + */ + role: Role; + /** + * Built-in service to link to. + */ + service: BuiltInLinkItem; + /** + * URL to link to. + */ + href: string; +} +export namespace NavbarConfigurationInputExternalLinksInner { +} + + diff --git a/frontend/src/app/openapi/model/navbar-configuration-input.ts b/frontend/src/app/openapi/model/navbar-configuration-input.ts new file mode 100644 index 000000000..bda864ba2 --- /dev/null +++ b/frontend/src/app/openapi/model/navbar-configuration-input.ts @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + * SPDX-License-Identifier: Apache-2.0 + * + * Capella Collaboration + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + + To generate a new version, run `make openapi` in the root directory of this repository. + */ + +import { NavbarConfigurationInputExternalLinksInner } from './navbar-configuration-input-external-links-inner'; + + +export interface NavbarConfigurationInput { + /** + * Links to display in the navigation bar. + */ + external_links?: Array<NavbarConfigurationInputExternalLinksInner>; +} + diff --git a/frontend/src/app/openapi/model/navbar-configuration-output.ts b/frontend/src/app/openapi/model/navbar-configuration-output.ts new file mode 100644 index 000000000..ac7d08408 --- /dev/null +++ b/frontend/src/app/openapi/model/navbar-configuration-output.ts @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + * SPDX-License-Identifier: Apache-2.0 + * + * Capella Collaboration + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + + To generate a new version, run `make openapi` in the root directory of this repository. + */ + +import { NavbarConfigurationInputExternalLinksInner } from './navbar-configuration-input-external-links-inner'; + + +export interface NavbarConfigurationOutput { + /** + * Links to display in the navigation bar. + */ + external_links: Array<NavbarConfigurationInputExternalLinksInner>; +} + diff --git a/frontend/src/app/settings/core/configuration-settings/configuration-settings.component.ts b/frontend/src/app/settings/core/configuration-settings/configuration-settings.component.ts index d4bb1cd46..bb98f35d5 100644 --- a/frontend/src/app/settings/core/configuration-settings/configuration-settings.component.ts +++ b/frontend/src/app/settings/core/configuration-settings/configuration-settings.component.ts @@ -8,6 +8,7 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { MatButton } from '@angular/material/button'; import { MatIcon } from '@angular/material/icon'; import { MetadataService } from 'src/app/general/metadata/metadata.service'; +import { NavBarService } from 'src/app/general/nav-bar/nav-bar.service'; import { EditorComponent } from 'src/app/helpers/editor/editor.component'; import { ToastService } from 'src/app/helpers/toast/toast.service'; import { ConfigurationSettingsService } from 'src/app/settings/core/configuration-settings/configuration-settings.service'; @@ -26,6 +27,7 @@ export class ConfigurationSettingsComponent implements OnInit { private configurationSettingsService: ConfigurationSettingsService, private toastService: ToastService, private metadataService: MetadataService, + private navbarService: NavBarService, ) {} ngOnInit(): void { @@ -52,6 +54,7 @@ export class ConfigurationSettingsComponent implements OnInit { ); this.fetchConfiguration(); this.metadataService.loadBackendMetadata().subscribe(); + this.navbarService.loadNavbarConfig().subscribe(); }, }); }