containers page wip
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 3s
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 3s
This commit is contained in:
parent
50056356f3
commit
4a2a9eba30
7
frontend/libs/shared/navbar/README.md
Normal file
7
frontend/libs/shared/navbar/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# navbar
|
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev).
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `nx test navbar` to execute the unit tests.
|
||||||
34
frontend/libs/shared/navbar/eslint.config.mjs
Normal file
34
frontend/libs/shared/navbar/eslint.config.mjs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import nx from '@nx/eslint-plugin';
|
||||||
|
import baseConfig from '../../../eslint.base.config.mjs';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
...baseConfig,
|
||||||
|
...nx.configs['flat/angular'],
|
||||||
|
...nx.configs['flat/angular-template'],
|
||||||
|
{
|
||||||
|
files: ['**/*.ts'],
|
||||||
|
rules: {
|
||||||
|
'@angular-eslint/directive-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'attribute',
|
||||||
|
prefix: 'lib',
|
||||||
|
style: 'camelCase',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@angular-eslint/component-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
prefix: 'lib',
|
||||||
|
style: 'kebab-case',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.html'],
|
||||||
|
// Override or add rules here
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
];
|
||||||
21
frontend/libs/shared/navbar/jest.config.ts
Normal file
21
frontend/libs/shared/navbar/jest.config.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export default {
|
||||||
|
displayName: 'navbar',
|
||||||
|
preset: '../../../jest.preset.js',
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
||||||
|
coverageDirectory: '../../../coverage/libs/shared/navbar',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.(ts|mjs|js|html)$': [
|
||||||
|
'jest-preset-angular',
|
||||||
|
{
|
||||||
|
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||||
|
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
||||||
|
snapshotSerializers: [
|
||||||
|
'jest-preset-angular/build/serializers/no-ng-attributes',
|
||||||
|
'jest-preset-angular/build/serializers/ng-snapshot',
|
||||||
|
'jest-preset-angular/build/serializers/html-comment',
|
||||||
|
],
|
||||||
|
};
|
||||||
20
frontend/libs/shared/navbar/project.json
Normal file
20
frontend/libs/shared/navbar/project.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "navbar",
|
||||||
|
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "libs/shared/navbar/src",
|
||||||
|
"prefix": "lib",
|
||||||
|
"projectType": "library",
|
||||||
|
"tags": [],
|
||||||
|
"targets": {
|
||||||
|
"test": {
|
||||||
|
"executor": "@nx/jest:jest",
|
||||||
|
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||||
|
"options": {
|
||||||
|
"jestConfig": "libs/shared/navbar/jest.config.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nx/eslint:lint"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
frontend/libs/shared/navbar/src/index.ts
Normal file
2
frontend/libs/shared/navbar/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './lib/navbar-shell.component';
|
||||||
|
export * from './lib/navbar.component';
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { NavbarComponent } from './navbar.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'frontend-navbar-shell',
|
||||||
|
imports: [NavbarComponent],
|
||||||
|
template: `
|
||||||
|
<frontend-navbar />
|
||||||
|
<ng-content />
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class NavbarShellComponent {}
|
||||||
|
|
||||||
37
frontend/libs/shared/navbar/src/lib/navbar.component.ts
Normal file
37
frontend/libs/shared/navbar/src/lib/navbar.component.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Component, inject } from '@angular/core';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatIcon } from '@angular/material/icon';
|
||||||
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
|
import { ThemeToggleService } from '@frontend/shared/theme-toggle';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'frontend-navbar',
|
||||||
|
imports: [MatToolbarModule, MatIcon, MatButtonModule],
|
||||||
|
template: `
|
||||||
|
<mat-toolbar>
|
||||||
|
<mat-icon>view_carousel</mat-icon>
|
||||||
|
<span>Docker Containers</span>
|
||||||
|
<span class="spacer"></span>
|
||||||
|
<button mat-icon-button (click)="switchTheme()">
|
||||||
|
<mat-icon>{{ isDarkTheme() ? 'light_mode' : 'dark_mode' }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-toolbar>
|
||||||
|
`,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
.spacer {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class NavbarComponent {
|
||||||
|
private readonly themeToggleService = inject(ThemeToggleService);
|
||||||
|
|
||||||
|
protected isDarkTheme = this.themeToggleService.isDark;
|
||||||
|
|
||||||
|
switchTheme() {
|
||||||
|
this.themeToggleService.toggleTheme();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
6
frontend/libs/shared/navbar/src/test-setup.ts
Normal file
6
frontend/libs/shared/navbar/src/test-setup.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||||
|
|
||||||
|
setupZoneTestEnv({
|
||||||
|
errorOnUnknownElements: true,
|
||||||
|
errorOnUnknownProperties: true,
|
||||||
|
});
|
||||||
28
frontend/libs/shared/navbar/tsconfig.json
Normal file
28
frontend/libs/shared/navbar/tsconfig.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2022",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.lib.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.spec.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extends": "../../../tsconfig.base.json",
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"enableI18nLegacyMessageIdFormat": false,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"strictInputAccessModifiers": true,
|
||||||
|
"strictTemplates": true
|
||||||
|
}
|
||||||
|
}
|
||||||
17
frontend/libs/shared/navbar/tsconfig.lib.json
Normal file
17
frontend/libs/shared/navbar/tsconfig.lib.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../../dist/out-tsc",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"inlineSources": true,
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/test-setup.ts",
|
||||||
|
"jest.config.ts",
|
||||||
|
"src/**/*.test.ts"
|
||||||
|
],
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
||||||
16
frontend/libs/shared/navbar/tsconfig.spec.json
Normal file
16
frontend/libs/shared/navbar/tsconfig.spec.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../../dist/out-tsc",
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es2016",
|
||||||
|
"types": ["jest", "node"]
|
||||||
|
},
|
||||||
|
"files": ["src/test-setup.ts"],
|
||||||
|
"include": [
|
||||||
|
"jest.config.ts",
|
||||||
|
"src/**/*.test.ts",
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -1,2 +1,2 @@
|
|||||||
export * from './lib/containers';
|
|
||||||
export * from './lib/types';
|
export * from './lib/types';
|
||||||
|
export * from './lib/ws.service';
|
||||||
|
|||||||
7
frontend/libs/web/containers/README.md
Normal file
7
frontend/libs/web/containers/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# containers
|
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev).
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `nx test containers` to execute the unit tests.
|
||||||
34
frontend/libs/web/containers/eslint.config.mjs
Normal file
34
frontend/libs/web/containers/eslint.config.mjs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import nx from '@nx/eslint-plugin';
|
||||||
|
import baseConfig from '../../../eslint.base.config.mjs';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
...baseConfig,
|
||||||
|
...nx.configs['flat/angular'],
|
||||||
|
...nx.configs['flat/angular-template'],
|
||||||
|
{
|
||||||
|
files: ['**/*.ts'],
|
||||||
|
rules: {
|
||||||
|
'@angular-eslint/directive-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'attribute',
|
||||||
|
prefix: 'lib',
|
||||||
|
style: 'camelCase',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@angular-eslint/component-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
prefix: 'lib',
|
||||||
|
style: 'kebab-case',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.html'],
|
||||||
|
// Override or add rules here
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
];
|
||||||
21
frontend/libs/web/containers/jest.config.ts
Normal file
21
frontend/libs/web/containers/jest.config.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export default {
|
||||||
|
displayName: 'containers',
|
||||||
|
preset: '../../../jest.preset.js',
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
||||||
|
coverageDirectory: '../../../coverage/libs/web/containers',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.(ts|mjs|js|html)$': [
|
||||||
|
'jest-preset-angular',
|
||||||
|
{
|
||||||
|
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||||
|
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
||||||
|
snapshotSerializers: [
|
||||||
|
'jest-preset-angular/build/serializers/no-ng-attributes',
|
||||||
|
'jest-preset-angular/build/serializers/ng-snapshot',
|
||||||
|
'jest-preset-angular/build/serializers/html-comment',
|
||||||
|
],
|
||||||
|
};
|
||||||
20
frontend/libs/web/containers/project.json
Normal file
20
frontend/libs/web/containers/project.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "containers",
|
||||||
|
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "libs/web/containers/src",
|
||||||
|
"prefix": "lib",
|
||||||
|
"projectType": "library",
|
||||||
|
"tags": [],
|
||||||
|
"targets": {
|
||||||
|
"test": {
|
||||||
|
"executor": "@nx/jest:jest",
|
||||||
|
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||||
|
"options": {
|
||||||
|
"jestConfig": "libs/web/containers/jest.config.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nx/eslint:lint"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
frontend/libs/web/containers/src/index.ts
Normal file
1
frontend/libs/web/containers/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './lib/containers-shell.component';
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
|
import { ContainersComponent } from './containers.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'frontend-containers-shell',
|
||||||
|
imports: [ContainersComponent, MatToolbarModule, MatIconModule],
|
||||||
|
template: `
|
||||||
|
<div class="content">
|
||||||
|
<p class="description">View and manage your Docker containers here.</p>
|
||||||
|
<frontend-containers />
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
.content {
|
||||||
|
padding: 24px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
color: var(--sys-on-surface-variant);
|
||||||
|
margin-bottom: 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ContainersComponentShell {}
|
||||||
|
|
||||||
174
frontend/libs/web/containers/src/lib/containers.component.ts
Normal file
174
frontend/libs/web/containers/src/lib/containers.component.ts
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
import { NgClass } from '@angular/common';
|
||||||
|
import { Component, inject, OnInit } from '@angular/core';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { MatChipsModule } from '@angular/material/chips';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { StatsWsService } from '@frontend/shared/stats-ws';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'frontend-containers',
|
||||||
|
imports: [
|
||||||
|
MatCardModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatChipsModule,
|
||||||
|
MatButtonModule,
|
||||||
|
NgClass,
|
||||||
|
],
|
||||||
|
template: `
|
||||||
|
<div class="content-container">
|
||||||
|
@for (container of containers(); track container.id) {
|
||||||
|
<mat-card class="container-card">
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-icon
|
||||||
|
mat-card-avatar
|
||||||
|
[ngClass]="getStateClass(container.state)"
|
||||||
|
>
|
||||||
|
{{ getStateIcon(container.state) }}
|
||||||
|
</mat-icon>
|
||||||
|
<mat-card-title>{{ container.name }}</mat-card-title>
|
||||||
|
<mat-card-subtitle>{{ container.image }}</mat-card-subtitle>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="label">ID:</span>
|
||||||
|
<span class="value">{{ container.id.substring(0, 12) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="label">Status:</span>
|
||||||
|
<mat-chip [ngClass]="getStatusClass(container.status)">
|
||||||
|
{{ container.status }}
|
||||||
|
</mat-chip>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button mat-button color="primary">
|
||||||
|
<mat-icon>info</mat-icon> DETAILS
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
[disabled]="true"
|
||||||
|
[color]="container.state === 'running' ? 'warn' : 'primary'"
|
||||||
|
>
|
||||||
|
<mat-icon>{{
|
||||||
|
container.state === 'running' ? 'stop' : 'play_arrow'
|
||||||
|
}}</mat-icon>
|
||||||
|
{{ container.state === 'running' ? 'STOP' : 'START' }}
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
.content-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-card {
|
||||||
|
transition:
|
||||||
|
transform 0.2s,
|
||||||
|
box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-row {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--sys-on-surface-variant);
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-running {
|
||||||
|
color: #4caf50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-exited,
|
||||||
|
.state-stopped {
|
||||||
|
color: #f44336;
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-created,
|
||||||
|
.state-paused {
|
||||||
|
color: #ff9800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-chip-running {
|
||||||
|
background-color: rgba(76, 175, 80, 0.15) !important;
|
||||||
|
color: #4caf50 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-chip-exited,
|
||||||
|
.status-chip-stopped {
|
||||||
|
background-color: rgba(244, 67, 54, 0.15) !important;
|
||||||
|
color: #f44336 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-chip-created,
|
||||||
|
.status-chip-paused {
|
||||||
|
background-color: rgba(255, 152, 0, 0.15) !important;
|
||||||
|
color: #ff9800 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card-actions {
|
||||||
|
padding: 8px 16px 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ContainersComponent implements OnInit {
|
||||||
|
private readonly service = inject(StatsWsService);
|
||||||
|
protected containers = this.service.containers;
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.service.initContainers();
|
||||||
|
}
|
||||||
|
|
||||||
|
getStateIcon(state: string): string {
|
||||||
|
switch (state) {
|
||||||
|
case 'running':
|
||||||
|
return 'play_circle';
|
||||||
|
case 'exited':
|
||||||
|
return 'stop_circle';
|
||||||
|
case 'created':
|
||||||
|
return 'fiber_new';
|
||||||
|
case 'paused':
|
||||||
|
return 'pause_circle';
|
||||||
|
default:
|
||||||
|
return 'help_circle';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getStateClass(state: string): string {
|
||||||
|
return `state-${state.toLowerCase()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getStatusClass(status: string): string {
|
||||||
|
if (status.includes('Up')) return 'status-chip-running';
|
||||||
|
if (status.includes('Exited')) return 'status-chip-exited';
|
||||||
|
if (status.includes('Created')) return 'status-chip-created';
|
||||||
|
if (status.includes('Paused')) return 'status-chip-paused';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
6
frontend/libs/web/containers/src/test-setup.ts
Normal file
6
frontend/libs/web/containers/src/test-setup.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||||
|
|
||||||
|
setupZoneTestEnv({
|
||||||
|
errorOnUnknownElements: true,
|
||||||
|
errorOnUnknownProperties: true,
|
||||||
|
});
|
||||||
28
frontend/libs/web/containers/tsconfig.json
Normal file
28
frontend/libs/web/containers/tsconfig.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2022",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.lib.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.spec.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extends": "../../../tsconfig.base.json",
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"enableI18nLegacyMessageIdFormat": false,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"strictInputAccessModifiers": true,
|
||||||
|
"strictTemplates": true
|
||||||
|
}
|
||||||
|
}
|
||||||
17
frontend/libs/web/containers/tsconfig.lib.json
Normal file
17
frontend/libs/web/containers/tsconfig.lib.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../../dist/out-tsc",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"inlineSources": true,
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/test-setup.ts",
|
||||||
|
"jest.config.ts",
|
||||||
|
"src/**/*.test.ts"
|
||||||
|
],
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
||||||
16
frontend/libs/web/containers/tsconfig.spec.json
Normal file
16
frontend/libs/web/containers/tsconfig.spec.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../../dist/out-tsc",
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es2016",
|
||||||
|
"types": ["jest", "node"]
|
||||||
|
},
|
||||||
|
"files": ["src/test-setup.ts"],
|
||||||
|
"include": [
|
||||||
|
"jest.config.ts",
|
||||||
|
"src/**/*.test.ts",
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
609
frontend/package-lock.json
generated
609
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -32,13 +32,13 @@
|
|||||||
"@angular/compiler-cli": "20.0.0",
|
"@angular/compiler-cli": "20.0.0",
|
||||||
"@angular/language-service": "20.0.0",
|
"@angular/language-service": "20.0.0",
|
||||||
"@eslint/js": "9.28.0",
|
"@eslint/js": "9.28.0",
|
||||||
"@nx/angular": "21.1.2",
|
"@nx/angular": "21.1.3",
|
||||||
"@nx/eslint": "21.1.2",
|
"@nx/eslint": "21.1.3",
|
||||||
"@nx/eslint-plugin": "21.1.2",
|
"@nx/eslint-plugin": "21.1.3",
|
||||||
"@nx/jest": "21.1.2",
|
"@nx/jest": "21.1.3",
|
||||||
"@nx/js": "21.1.2",
|
"@nx/js": "21.1.3",
|
||||||
"@nx/web": "21.1.2",
|
"@nx/web": "21.1.3",
|
||||||
"@nx/workspace": "21.1.2",
|
"@nx/workspace": "21.1.3",
|
||||||
"@schematics/angular": "20.0.0",
|
"@schematics/angular": "20.0.0",
|
||||||
"@swc-node/register": "1.10.10",
|
"@swc-node/register": "1.10.10",
|
||||||
"@swc/core": "1.11.29",
|
"@swc/core": "1.11.29",
|
||||||
@ -52,7 +52,7 @@
|
|||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-environment-jsdom": "29.7.0",
|
"jest-environment-jsdom": "29.7.0",
|
||||||
"jest-preset-angular": "14.6.0",
|
"jest-preset-angular": "14.6.0",
|
||||||
"nx": "21.1.2",
|
"nx": "21.1.3",
|
||||||
"prettier": "3.5.3",
|
"prettier": "3.5.3",
|
||||||
"ts-jest": "29.3.4",
|
"ts-jest": "29.3.4",
|
||||||
"ts-node": "10.9.2",
|
"ts-node": "10.9.2",
|
||||||
|
|||||||
@ -1,74 +1,15 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { Component } from '@angular/core';
|
||||||
import { Component, OnInit, inject } from '@angular/core';
|
import { RouterOutlet } from '@angular/router';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { NavbarShellComponent } from '@frontend/shared/navbar';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
|
||||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
|
||||||
import { RouterModule } from '@angular/router';
|
|
||||||
import { StatsWsService } from '@frontend/shared/stats-ws';
|
|
||||||
import { ThemeToggleService } from '@frontend/shared/theme-toggle';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [NavbarShellComponent, RouterOutlet],
|
||||||
RouterModule,
|
|
||||||
MatButtonModule,
|
|
||||||
MatCardModule,
|
|
||||||
MatIconModule,
|
|
||||||
MatToolbarModule,
|
|
||||||
CommonModule,
|
|
||||||
],
|
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
template: `
|
template: `
|
||||||
<mat-toolbar>
|
<frontend-navbar-shell>
|
||||||
<span>Docker Containers</span>
|
<router-outlet />
|
||||||
<span class="spacer"></span>
|
</frontend-navbar-shell>
|
||||||
<button mat-icon-button (click)="switchTheme()">
|
|
||||||
<mat-icon>{{ isDarkTheme() ? 'light_mode' : 'dark_mode' }}</mat-icon>
|
|
||||||
</button>
|
|
||||||
</mat-toolbar>
|
|
||||||
|
|
||||||
<div class="content-container">
|
|
||||||
@for (container of containers(); track container.id) {
|
|
||||||
<mat-card class="container">
|
|
||||||
<mat-card-header>
|
|
||||||
<mat-card-title>{{ container.name }}</mat-card-title>
|
|
||||||
</mat-card-header>
|
|
||||||
<mat-card-content>
|
|
||||||
<p>ID: {{ container.id }}</p>
|
|
||||||
<p>Image: {{ container.image }}</p>
|
|
||||||
<p>Status: {{ container.status }}</p>
|
|
||||||
<p>State: {{ container.state }}</p>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<router-outlet></router-outlet>
|
|
||||||
`,
|
`,
|
||||||
styles: [
|
|
||||||
`
|
|
||||||
.spacer {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
.content-container {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent {}
|
||||||
private readonly service = inject(StatsWsService);
|
|
||||||
private readonly themeToggleService = inject(ThemeToggleService);
|
|
||||||
protected containers = this.service.containers;
|
|
||||||
|
|
||||||
// Add this to check current theme for the template
|
|
||||||
protected isDarkTheme = () => this.themeToggleService.isDark();
|
|
||||||
|
|
||||||
switchTheme() {
|
|
||||||
this.themeToggleService.toggleTheme();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.service.initContainers();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,3 +1,13 @@
|
|||||||
import { Route } from '@angular/router';
|
import { Route } from '@angular/router';
|
||||||
|
|
||||||
export const appRoutes: Route[] = [];
|
export const appRoutes: Route[] = [
|
||||||
|
{
|
||||||
|
path: 'containers',
|
||||||
|
loadComponent: () =>
|
||||||
|
import('@frontend/web/container').then((m) => m.ContainersComponentShell),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '**',
|
||||||
|
redirectTo: 'containers',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
@ -1,16 +1,35 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/icon?family=Material+Icons');
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: Roboto, 'Helvetica Neue', sans-serif;
|
font-family: Roboto, 'Helvetica Neue', sans-serif;
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
transition: background-color 0.3s ease;
|
color: rgba(0, 0, 0, 0.87);
|
||||||
|
transition:
|
||||||
|
background-color 0.3s ease,
|
||||||
|
color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.dark-theme {
|
body.dark-theme {
|
||||||
background-color: #303030;
|
background-color: #303030;
|
||||||
|
color: rgba(255, 255, 255, 0.87);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this for the mat-card styling
|
||||||
|
.container {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
transition:
|
||||||
|
background-color 0.3s ease,
|
||||||
|
color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure mat-card content changes color appropriately
|
||||||
|
body.dark-theme .mat-mdc-card {
|
||||||
|
--mdc-elevated-card-container-color: #424242;
|
||||||
|
color: rgba(255, 255, 255, 0.87);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,8 +16,12 @@
|
|||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@frontend/shared/environment": ["libs/shared/environment/src/index.ts"],
|
"@frontend/shared/environment": ["libs/shared/environment/src/index.ts"],
|
||||||
|
"@frontend/shared/navbar": ["libs/shared/navbar/src/index.ts"],
|
||||||
"@frontend/shared/stats-ws": ["libs/shared/stats-ws/src/index.ts"],
|
"@frontend/shared/stats-ws": ["libs/shared/stats-ws/src/index.ts"],
|
||||||
"@frontend/shared/theme-toggle": ["libs/shared/theme-toggle/src/index.ts"]
|
"@frontend/shared/theme-toggle": [
|
||||||
|
"libs/shared/theme-toggle/src/index.ts"
|
||||||
|
],
|
||||||
|
"@frontend/web/container": ["libs/web/containers/src/index.ts"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "tmp"]
|
"exclude": ["node_modules", "tmp"]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user