Skip to content

Commit

Permalink
feat: Add the ability to set a company logo and show environment badge
Browse files Browse the repository at this point in the history
Add the ability to set a logo that will be displayed next to the Capella text. Add a badge that can
show the current environment with different UI depending on if the environment is productive. The
badge is enabled during development by default.
  • Loading branch information
zusorio committed Dec 2, 2024
1 parent 2d79dbf commit d22bf52
Show file tree
Hide file tree
Showing 22 changed files with 365 additions and 14 deletions.
29 changes: 25 additions & 4 deletions backend/capellacollab/navbar/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@
import fastapi
from sqlalchemy import orm

from capellacollab.core import database
from capellacollab.core import (
CLUSTER_DEVELOPMENT_MODE,
LOCAL_DEVELOPMENT_MODE,
database,
)
from capellacollab.settings.configuration import core as config_core
from capellacollab.settings.configuration import models as config_models
from capellacollab.settings.configuration.models import BadgeVariant

router = fastapi.APIRouter()

Expand All @@ -18,6 +23,22 @@
def get_navbar(db: orm.Session = fastapi.Depends(database.get_db)):
cfg = config_core.get_global_configuration(db)

return config_models.NavbarConfiguration.model_validate(
cfg.navbar.model_dump()
)
if cfg.navbar.badge.show:
if cfg.navbar.badge.text == "auto":
if CLUSTER_DEVELOPMENT_MODE:
cfg.navbar.badge.text = "Cluster Development"

Check warning on line 29 in backend/capellacollab/navbar/routes.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/navbar/routes.py#L29

Added line #L29 was not covered by tests
elif LOCAL_DEVELOPMENT_MODE:
cfg.navbar.badge.text = "Local Development"

Check warning on line 31 in backend/capellacollab/navbar/routes.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/navbar/routes.py#L31

Added line #L31 was not covered by tests
else:
cfg.navbar.badge.text = (

Check warning on line 33 in backend/capellacollab/navbar/routes.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/navbar/routes.py#L33

Added line #L33 was not covered by tests
cfg.metadata.environment or "Unknown Environment"
)

if cfg.navbar.badge.variant == BadgeVariant.AUTO:
words = ["dev", "development", "unknown", "staging"]

Check warning on line 38 in backend/capellacollab/navbar/routes.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/navbar/routes.py#L38

Added line #L38 was not covered by tests
if any(word in cfg.navbar.badge.text.lower() for word in words):
cfg.navbar.badge.variant = BadgeVariant.WARNING

Check warning on line 40 in backend/capellacollab/navbar/routes.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/navbar/routes.py#L40

Added line #L40 was not covered by tests
else:
cfg.navbar.badge.variant = BadgeVariant.SUCCESS

Check warning on line 42 in backend/capellacollab/navbar/routes.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/navbar/routes.py#L42

Added line #L42 was not covered by tests

return cfg.navbar
31 changes: 30 additions & 1 deletion backend/capellacollab/settings/configuration/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from sqlalchemy import orm

from capellacollab import core
from capellacollab.core import database
from capellacollab.core import DEVELOPMENT_MODE, database
from capellacollab.core import pydantic as core_pydantic
from capellacollab.users import models as users_models

Expand Down Expand Up @@ -69,6 +69,27 @@ class CustomNavbarLink(NavbarLink):
)


class BadgeVariant(str, enum.Enum):
AUTO = "auto"
WARNING = "warning"
SUCCESS = "success"


class Badge(core_pydantic.BaseModelStrict):
show: bool = pydantic.Field(
default=True,
description="Show a badge with the current environment.",
)
variant: BadgeVariant = pydantic.Field(
default=BadgeVariant.AUTO,
description="Color of the badge.",
)
text: str | t.Literal["auto"] = pydantic.Field(
default="auto",
description="Text to display in the badge. Use 'auto' to display the environment name.",
)


class NavbarConfiguration(core_pydantic.BaseModelStrict):
external_links: collections_abc.Sequence[
BuiltInNavbarLink | CustomNavbarLink
Expand Down Expand Up @@ -105,6 +126,14 @@ class NavbarConfiguration(core_pydantic.BaseModelStrict):
),
description="Links to display in the navigation bar.",
)
logo_url: str | None = pydantic.Field(
default=None,
description="URL to a logo to display in the navigation bar.",
)
badge: Badge = pydantic.Field(
default=Badge(show=DEVELOPMENT_MODE),
description="Badge to display in the navigation bar.",
)


class FeedbackIntervalConfiguration(core_pydantic.BaseModelStrict):
Expand Down
22 changes: 18 additions & 4 deletions docs/docs/admin/configure-for-your-org.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ metadata:
environment: '-'
```
## Navigation Bar
## Logo and Navigation Bar
You can edit the links in the navigation bar. This can be useful if you want to
link to external resources or if you are not using the default monitoring
setup.
You can edit the logo, badge, and links in the navigation bar. This can be
useful to brand the Collaboration Manager for your organization, remind users
which environment they are in, or link to external resources.
```yaml
navbar:
Expand All @@ -45,6 +45,11 @@ navbar:
- name: Documentation
service: documentation
role: user
logo_url: null
badge:
show: true
variant: auto
text: auto
```
In addition to the default service links, you can add your own by using `href`
Expand All @@ -63,6 +68,15 @@ 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.

To show the logo in the navigation bar, set the `logo_url` field to the URL of
the image you want to use.

The badge can be used to show the environment the user is in. The `variant`
field can be set to `auto` (it will be determined by the environment),
`success`, or `warning`. The `text` field will use the environment name if set
to `auto`, or you can specify a custom text. If you don't want to show the
badge, set `show` to `false`.

## Feedback

!!! info "Configure SMTP server for feedback"
Expand Down
1 change: 1 addition & 0 deletions frontend/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const config: StorybookConfig = {
docs: {
autodocs: 'tag',
},
staticDirs: [{ from: './test-assets', to: '/test-assets' }],
core: {
disableTelemetry: true,
enableCrashReports: false,
Expand Down
9 changes: 9 additions & 0 deletions frontend/.storybook/test-assets/narrow_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions frontend/.storybook/test-assets/wide_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 4 additions & 5 deletions frontend/src/app/general/header/header.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@

<div class="hidden xl:block">
<div class="flex min-h-[65px] items-center justify-between gap-2">
<div class="ml-5 basis-1/3 select-none text-2xl text-primary">
Capella Collaboration Manager
</div>
<app-logo class="ml-5"></app-logo>

@if (navBarService.navbarItems$ | async) {
<div class="flex basis-1/3 justify-center gap-2">
<div class="flex grow justify-center gap-2">
@for (item of navBarService.navbarItems$ | async; track item.name) {
@if (userService.validateUserRole(item.requiredRole)) {
@if (item.href) {
Expand Down Expand Up @@ -52,7 +51,7 @@
</div>
}

<div class="!mr-5 hidden basis-1/3 items-center justify-end gap-2 xl:flex">
<div class="!mr-5 hidden items-center justify-end gap-2 xl:flex">
<mat-menu #profileMenu="matMenu" class="flex items-center">
<a
class="px-[15px] text-left"
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/app/general/header/header.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { NavBarService } from 'src/app/general/nav-bar/nav-bar.service';
import { AuthenticationWrapperService } from '../../services/auth/auth.service';
import { OwnUserWrapperService } from '../../services/user/user.service';
import { BreadcrumbsComponent } from '../breadcrumbs/breadcrumbs.component';
import { LogoComponent } from '../logo/logo.component';

@Component({
selector: 'app-header',
Expand All @@ -28,6 +29,7 @@ import { BreadcrumbsComponent } from '../breadcrumbs/breadcrumbs.component';
MatMenuTrigger,
BreadcrumbsComponent,
AsyncPipe,
LogoComponent,
],
})
export class HeaderComponent {
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/app/general/header/header.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
mockOwnUserWrapperServiceProvider,
mockUser,
} from 'src/storybook/user';
import { BadgeOutput } from '../../openapi';
import { NavBarItem, NavBarService } from '../nav-bar/nav-bar.service';
import { HeaderComponent } from './header.component';

Expand Down Expand Up @@ -52,6 +53,12 @@ class MockNavbarService implements Partial<NavBarService> {
href: '#',
},
]);
readonly logoUrl$ = of<string | undefined>(undefined);
readonly badge$ = of<BadgeOutput | undefined>({
variant: 'warning',
text: 'Storybook',
show: true,
});
}

export const NormalUser: Story = {
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/app/general/logo/logo.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!--
~ SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
~ SPDX-License-Identifier: Apache-2.0
-->

<div class="flex select-none items-center gap-2">
@if (navBarService.logoUrl$ | async; as logoUrl) {
<img [src]="logoUrl" aria-hidden="true" class="max-h-8 max-w-24" />
}

<div class="text-2xl font-semibold text-primary">CCM</div>

@if ((navBarService.badge$ | async)?.show) {
<div
class="rounded-md px-1 py-0.5 text-xs font-semibold uppercase tracking-wide text-white"
[class.bg-hazard]="(navBarService.badge$ | async)?.variant === 'warning'"
[class.bg-green-600]="
(navBarService.badge$ | async)?.variant === 'success'
"
>
{{ (navBarService.badge$ | async)?.text }}
</div>
}
</div>
17 changes: 17 additions & 0 deletions frontend/src/app/general/logo/logo.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
* SPDX-License-Identifier: Apache-2.0
*/
import { AsyncPipe } from '@angular/common';
import { Component } from '@angular/core';
import { NavBarService } from '../nav-bar/nav-bar.service';

@Component({
selector: 'app-logo',
standalone: true,
templateUrl: './logo.component.html',
imports: [AsyncPipe],
})
export class LogoComponent {
constructor(public navBarService: NavBarService) {}
}
Loading

0 comments on commit d22bf52

Please sign in to comment.