Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"builder": "@angular/build:dev-server",
"options": {
"proxyConfig": "proxy/proxy.conf.mjs"
},
Expand Down
17,547 changes: 6,285 additions & 11,262 deletions frontend/package-lock.json

Large diffs are not rendered by default.

46 changes: 16 additions & 30 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,51 +9,37 @@
"test": "ng test",
"test:ci": "ng test --watch=false --browsers=ChromeHeadless"
},
"prettier": {
"printWidth": 100,
"singleAttributePerLine": true,
"singleQuote": true,
"overrides": [
{
"files": "*.html",
"options": {
"parser": "angular"
}
}
]
},
"private": true,
"dependencies": {
"@angular-devkit/build-angular": "^20.3.2",
"@angular/common": "^20.3.0",
"@angular/compiler": "^20.3.0",
"@angular/core": "^20.3.0",
"@angular/forms": "^20.3.0",
"@angular/platform-browser": "^20.3.0",
"@angular/router": "^20.3.0",
"@angular/common": "^21.2.6",
"@angular/compiler": "^21.2.6",
"@angular/core": "^21.2.6",
"@angular/forms": "^21.2.6",
"@angular/platform-browser": "^21.2.6",
"@angular/router": "^21.2.6",
"@ngx-translate/core": "^17.0.0",
"@ngx-translate/http-loader": "^17.0.0",
"@primeuix/themes": "^1.2.3",
"cron-validate": "^1.5.2",
"dayjs": "^1.11.19",
"@primeuix/themes": "^2.0.3",
"cron-validate": "^1.5.3",
"dayjs": "^1.11.20",
"primeicons": "^7.0.0",
"primeng": "^20.1.2",
"primeng": "^21.1.4",
"rxjs": "~7.8.0",
"tslib": "^2.3.0"
"tslib": "^2.8.1"
},
"devDependencies": {
"@angular/build": "^20.3.2",
"@angular/cli": "^20.3.2",
"@angular/compiler-cli": "^20.3.0",
"@angular/build": "^21.2.5",
"@angular/cli": "^21.2.5",
"@angular/compiler-cli": "^21.2.6",
"@types/jasmine": "~5.1.0",
"baseline-browser-mapping": "^2.9.14",
"baseline-browser-mapping": "^2.10.12",
"jasmine-core": "~5.9.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"prettier": "^3.6.2",
"prettier": "^3.8.1",
"typescript": "~5.9.2"
}
}
14 changes: 11 additions & 3 deletions frontend/public/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
"0": "Tug",
"1": "tainer"
},
"THEMES": {
"AUTO": "Auto",
"LIGHT": "Light",
"DARK": "Dark"
},
"NAV": {
"AUTH": "Tugtainer. Authorization.",
"HOSTS": "Tugtainer. Hosts.",
Expand Down Expand Up @@ -55,9 +60,12 @@
"MEDIUM": "Average complexity",
"STRONG": "Complex password"
},
"PASSWORD_HINTS": ["At least one lowercase", "At least one uppercase", "At least one numeric"],
"OR": "Or",
"LOGIN_WITH_OIDC": "Login with OIDC"
"PASSWORD_HINTS": [
"At least one lowercase",
"At least one uppercase",
"At least one numeric"
],
"SIGNIN_WITH_OIDC": "Sign-in with OIDC"
},
"HOSTS": {
"NO_HOSTS_FOUND": "No hosts found",
Expand Down
3 changes: 1 addition & 2 deletions frontend/public/i18n/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@
"STRONG": "复杂密码"
},
"PASSWORD_HINTS": ["至少一个小写字母", "至少一个大写字母", "至少一个数字"],
"OR": "或",
"LOGIN_WITH_OIDC": "使用 OIDC 登录"
"SIGNIN_WITH_OIDC": "使用 OIDC 登录"
},
"HOSTS": {
"NO_HOSTS_FOUND": "未找到主机",
Expand Down
20 changes: 16 additions & 4 deletions frontend/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
ApplicationConfig,
inject,
LOCALE_ID,
provideAppInitializer,
provideBrowserGlobalErrorListeners,
Expand All @@ -9,15 +10,18 @@ import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { authInterceptor } from './core/interceptors/auth-interceptor';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { provideTranslateLoader, provideTranslateService } from '@ngx-translate/core';
import {
provideTranslateLoader,
provideTranslateService,
} from '@ngx-translate/core';
import { localeInitializer } from './core/initializers/locale-initializer';
import { providePrimeNG } from 'primeng/config';
import Aura from '@primeuix/themes/aura';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { MessageService } from 'primeng/api';
import { definePreset } from '@primeuix/themes';
import { SlickTranslationLoader } from './core/services/slick-translation-loader.service';
import { supportedLocales } from './app.consts';
import { AppThemeService } from './core/services/theme.service';

const themePreset = definePreset(Aura, {
semantic: {
Expand All @@ -43,7 +47,6 @@ export const appConfig: ApplicationConfig = {
provideZonelessChangeDetection(),
provideRouter(routes),
provideHttpClient(withInterceptors([authInterceptor])),
provideAnimationsAsync(),
provideTranslateService({
loader: provideTranslateLoader(SlickTranslationLoader),
fallbackLang: 'en',
Expand All @@ -52,16 +55,25 @@ export const appConfig: ApplicationConfig = {
{
provide: LOCALE_ID,
useFactory: () => {
const locale = navigator.language ? navigator.language.split('-')[0] : 'en';
const locale = navigator.language
? navigator.language.split('-')[0]
: 'en';
return supportedLocales.find((l) => l === locale) || 'en';
},
},
providePrimeNG({
theme: {
preset: themePreset,
options: {
darkModeSelector: '.app-dark',
},
},
}),
provideAppInitializer(() => localeInitializer()),
provideAppInitializer(() => {
const themeService = inject(AppThemeService);
themeService.init();
}),
MessageService,
],
};
3 changes: 3 additions & 0 deletions frontend/src/app/app.enums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum EStorageKey {
THEME = 'tugtainer-theme',
}
147 changes: 79 additions & 68 deletions frontend/src/app/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,88 +2,99 @@

<p-drawer
[visible]="menuOpened()"
(visibleChange)="menuOpened.set($event)"
>
(visibleChange)="menuOpened.set($event)">
<ng-template #header>
<app-logo></app-logo>
</ng-template>
<p-panel-menu

<p-menu
[model]="menuItems$ | async"
(click)="menuOpened.set(false)"
></p-panel-menu>
(click)="menuOpened.set(false)">
</p-menu>

<ng-template #footer>
<p-select
fluid
[options]="themes$ | async"
optionValue="value"
optionLabel="label"
[ngModel]="theme$ | async"
(ngModelChange)="selectTheme($event)">
<ng-template #dropdownicon>
<i class="pi pi-palette"></i>
</ng-template>
<ng-template
#item
let-item>
<span class="theme-select-item">
{{ item.label }}
<i [class]="item.icon"></i>
</span>
</ng-template>
</p-select>
</ng-template>
</p-drawer>

@if (isToolbarVisible()) {
<p-toolbar>
<ng-template #start>
<p-toolbar>
<ng-template #start>
<p-button
icon="pi pi-bars"
text
severity="contrast"
(onClick)="menuOpened.set(true)"></p-button>
<app-logo></app-logo>
</ng-template>

<ng-template #end>
<section class="app-toolbar-end">
<span> </span>
@if (version.value(); as data) {
<p-tag severity="contrast"> {{ data.image_version }} </p-tag>
} @if (isUpdateAvailable.value()?.is_available) {
<p-tag
severity="success"
(click)="showNewVersionDialog.set(true)">
{{ 'MENU.UPDATE' | translate }}
</p-tag>
} @if (!isAuthDisabled.value()) {
<p-button
icon="pi pi-bars"
text
severity="contrast"
(onClick)="menuOpened.set(true)"
></p-button>
<app-logo></app-logo>
</ng-template>
<ng-template #end>
<section class="app-menu-end">
<span class="app-menu-end-version"> </span>
@if (version.value(); as data) {
<p-tag severity="contrast">
{{ data.image_version }}
</p-tag>
}
@if (isUpdateAvailable.value()?.is_available) {
<p-tag
severity="success"
(click)="showNewVersionDialog.set(true)"
>
{{ 'MENU.UPDATE' | translate }}
</p-tag>
}
@if (!isAuthDisabled.value()) {
<p-button
severity="contrast"
icon="pi pi-sign-out"
text
(onClick)="logout()"
></p-button>
}
</section>
</ng-template>
</p-toolbar>
icon="pi pi-sign-out"
text
(onClick)="logout()"></p-button>
}
</section>
</ng-template>
</p-toolbar>
}

<section class="app-content">
<router-outlet></router-outlet>
</section>

@defer {
<p-dialog
[header]="'MENU.UPDATE' | translate"
[modal]="true"
[draggable]="false"
[style]="{ width: '35rem' }"
[visible]="showNewVersionDialog()"
(visibleChange)="showNewVersionDialog.set($event)"
>
@for (item of 'MENU.UPDATE_NOTES' | translate; track item) {
<span>
{{ item }}
</span>
}
<ng-template #footer>
<p-button
icon="pi pi-link"
severity="success"
[label]="'UPDATE_DIALOGUE.DEPLOY_GUIDELINE' | translate"
(onClick)="openDeployGuideline()"
></p-button>
<p-button
icon="pi pi-link"
severity="success"
[label]="'UPDATE_DIALOGUE.RELEASE_NOTES' | translate"
(onClick)="openReleaseNotes()"
></p-button>
</ng-template>
</p-dialog>
<p-dialog
[header]="'MENU.UPDATE' | translate"
[modal]="true"
[draggable]="false"
[style]="{ width: '35rem' }"
[visible]="showNewVersionDialog()"
(visibleChange)="showNewVersionDialog.set($event)">
@for (item of 'MENU.UPDATE_NOTES' | translate; track item) {
<span> {{ item }} </span>
}
<ng-template #footer>
<p-button
icon="pi pi-link"
severity="success"
[label]="'UPDATE_DIALOGUE.DEPLOY_GUIDELINE' | translate"
(onClick)="openDeployGuideline()"></p-button>
<p-button
icon="pi pi-link"
severity="success"
[label]="'UPDATE_DIALOGUE.RELEASE_NOTES' | translate"
(onClick)="openReleaseNotes()"></p-button>
</ng-template>
</p-dialog>
}
36 changes: 28 additions & 8 deletions frontend/src/app/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,37 @@
width: 100%;
height: 100%;

p-drawer {
--p-drawer-content-padding: 0;

p-menu {
--p-menu-border-color: transparent;
--p-menu-item-padding: 0.5rem 1.25rem;

::ng-deep .p-menu-item-link-active {
--p-menu-item-icon-color: var(--p-button-text-primary-color);
}
}

.theme-select-item {
display: inline-flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
}

p-toolbar {
width: 100%;
}

.app-menu-end {
display: flex;
align-items: center;
gap: 12px;
white-space: nowrap;
& > * {
cursor: pointer;
.app-toolbar-end {
display: flex;
align-items: center;
gap: 12px;
white-space: nowrap;
& > * {
cursor: pointer;
}
}
}

Expand Down
Loading