containers update + docker file build
Some checks failed
Build and Push Docker Image / build-and-push (push) Has been cancelled
Some checks failed
Build and Push Docker Image / build-and-push (push) Has been cancelled
This commit is contained in:
parent
dac64ac41a
commit
7dd003e502
29
.gitea/workflows/build-push.yaml
Normal file
29
.gitea/workflows/build-push.yaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
name: Build and Push Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Harbor
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: harbor.galpodlipnik.com
|
||||||
|
username: ${{ secrets.HARBOR_USERNAME }}
|
||||||
|
password: ${{ secrets.HARBOR_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: harbor.galpodlipnik.com/project/docker-inspector:latest
|
||||||
@ -1,19 +0,0 @@
|
|||||||
name: Gitea Actions Demo
|
|
||||||
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
|
|
||||||
on: [push]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
Explore-Gitea-Actions:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
|
|
||||||
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
|
|
||||||
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
|
|
||||||
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
|
||||||
- name: List files in the repository
|
|
||||||
run: |
|
|
||||||
ls ${{ gitea.workspace }}
|
|
||||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
|
||||||
55
Dockerfile
Normal file
55
Dockerfile
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# Build frontend
|
||||||
|
FROM node:20-alpine AS frontend-builder
|
||||||
|
WORKDIR /app/frontend
|
||||||
|
COPY frontend/package*.json ./
|
||||||
|
RUN npm ci --legacy-peer-deps
|
||||||
|
COPY frontend/ ./
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Build backend
|
||||||
|
FROM node:20-alpine AS backend-builder
|
||||||
|
WORKDIR /app/backend
|
||||||
|
COPY backend/package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY backend/ ./
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Final image
|
||||||
|
FROM node:20-alpine
|
||||||
|
RUN apk add --no-cache tini
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Create a non-root user
|
||||||
|
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
|
||||||
|
|
||||||
|
# Copy built backend
|
||||||
|
COPY --from=backend-builder /app/backend/dist ./dist
|
||||||
|
COPY --from=backend-builder /app/backend/package*.json ./
|
||||||
|
|
||||||
|
# Copy built frontend
|
||||||
|
COPY --from=frontend-builder /app/frontend/dist/frontend ./public
|
||||||
|
|
||||||
|
# Install production dependencies only
|
||||||
|
RUN npm ci --omit=dev
|
||||||
|
|
||||||
|
# Create a startup script to serve both frontend and backend
|
||||||
|
COPY --chmod=755 <<EOF /app/start.sh
|
||||||
|
|
||||||
|
#!/bin/sh
|
||||||
|
# Make environment variables available to frontend
|
||||||
|
env | grep DOCKER_ > /app/public/env.js
|
||||||
|
echo "window.env = {" >> /app/public/env.js
|
||||||
|
env | grep DOCKER_ | sed 's/\(.*\)=\(.*\)/ \1: "\2",/' >> /app/public/env.js
|
||||||
|
echo "};" >> /app/public/env.js
|
||||||
|
|
||||||
|
# Start the server
|
||||||
|
node dist/index.js
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Set ownership
|
||||||
|
RUN chown -R appuser:appgroup /app
|
||||||
|
|
||||||
|
USER appuser
|
||||||
|
EXPOSE 3000
|
||||||
|
ENTRYPOINT ["/sbin/tini", "--"]
|
||||||
|
CMD ["/app/start.sh"]
|
||||||
17
frontend/libs/shared/environment/src/lib/env.service.ts
Normal file
17
frontend/libs/shared/environment/src/lib/env.service.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
env: Record<string, string>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class EnvService {
|
||||||
|
getEnv(key: string): string | undefined {
|
||||||
|
return window.env?.[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -2,15 +2,18 @@ import { Component, inject } from '@angular/core';
|
|||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatIcon } from '@angular/material/icon';
|
import { MatIcon } from '@angular/material/icon';
|
||||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
import { ThemeToggleService } from '@frontend/shared/theme-toggle';
|
import { ThemeToggleService } from '@frontend/shared/theme-toggle';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'frontend-navbar',
|
selector: 'frontend-navbar',
|
||||||
imports: [MatToolbarModule, MatIcon, MatButtonModule],
|
imports: [MatToolbarModule, MatIcon, MatButtonModule],
|
||||||
template: `
|
template: `
|
||||||
<mat-toolbar>
|
<mat-toolbar class="navbar">
|
||||||
<mat-icon>view_carousel</mat-icon>
|
<div (click)="goToHome()">
|
||||||
<span>Docker Containers</span>
|
<mat-icon>view_carousel</mat-icon>
|
||||||
|
<span>Docker Containers</span>
|
||||||
|
</div>
|
||||||
<span class="spacer"></span>
|
<span class="spacer"></span>
|
||||||
<button mat-icon-button (click)="switchTheme()">
|
<button mat-icon-button (click)="switchTheme()">
|
||||||
<mat-icon>{{ isDarkTheme() ? 'light_mode' : 'dark_mode' }}</mat-icon>
|
<mat-icon>{{ isDarkTheme() ? 'light_mode' : 'dark_mode' }}</mat-icon>
|
||||||
@ -22,16 +25,26 @@ import { ThemeToggleService } from '@frontend/shared/theme-toggle';
|
|||||||
.spacer {
|
.spacer {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
background-color: var(--mat-accent-500);
|
||||||
|
color: var(--mat-accent-contrast-500);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class NavbarComponent {
|
export class NavbarComponent {
|
||||||
private readonly themeToggleService = inject(ThemeToggleService);
|
private readonly themeToggleService = inject(ThemeToggleService);
|
||||||
|
private readonly router = inject(Router);
|
||||||
|
|
||||||
protected isDarkTheme = this.themeToggleService.isDark;
|
protected isDarkTheme = this.themeToggleService.isDark;
|
||||||
|
|
||||||
switchTheme() {
|
switchTheme() {
|
||||||
this.themeToggleService.toggleTheme();
|
this.themeToggleService.toggleTheme();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
goToHome() {
|
||||||
|
this.router.navigate(['/']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -2,6 +2,10 @@ import { OverlayContainer } from '@angular/cdk/overlay';
|
|||||||
import { computed, DOCUMENT, effect, inject, Injectable } from '@angular/core';
|
import { computed, DOCUMENT, effect, inject, Injectable } from '@angular/core';
|
||||||
import { stored } from '@mmstack/primitives';
|
import { stored } from '@mmstack/primitives';
|
||||||
|
|
||||||
|
function prefersDarkMode() {
|
||||||
|
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
@ -10,21 +14,17 @@ export class ThemeToggleService {
|
|||||||
inject(OverlayContainer).getContainerElement().classList;
|
inject(OverlayContainer).getContainerElement().classList;
|
||||||
private readonly dockument = inject(DOCUMENT);
|
private readonly dockument = inject(DOCUMENT);
|
||||||
|
|
||||||
private readonly theme = stored<'light' | 'dark'>('light', {
|
private readonly theme = stored<'light' | 'dark'>(
|
||||||
key: 'user-theme',
|
prefersDarkMode() ? 'dark' : 'light',
|
||||||
syncTabs: true,
|
{
|
||||||
});
|
key: 'user-theme',
|
||||||
|
syncTabs: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
public isDark = computed(() => this.theme() === 'dark');
|
public isDark = computed(() => this.theme() === 'dark');
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const prefersDark =
|
|
||||||
window.matchMedia &&
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
||||||
if (prefersDark && this.theme() === 'light') {
|
|
||||||
this.theme.set('dark');
|
|
||||||
}
|
|
||||||
|
|
||||||
effect(() =>
|
effect(() =>
|
||||||
this.isDark() ? this.addDarkThemeClass() : this.removeDarkThemeClass()
|
this.isDark() ? this.addDarkThemeClass() : this.removeDarkThemeClass()
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
# stats-ws
|
# ws
|
||||||
|
|
||||||
This library was generated with [Nx](https://nx.dev).
|
This library was generated with [Nx](https://nx.dev).
|
||||||
|
|
||||||
## Running unit tests
|
## Running unit tests
|
||||||
|
|
||||||
Run `nx test stats-ws` to execute the unit tests.
|
Run `nx test ws` to execute the unit tests.
|
||||||
@ -1,8 +1,8 @@
|
|||||||
export default {
|
export default {
|
||||||
displayName: 'stats-ws',
|
displayName: 'ws',
|
||||||
preset: '../../../jest.preset.js',
|
preset: '../../../jest.preset.js',
|
||||||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
||||||
coverageDirectory: '../../../coverage/libs/shared/stats-ws',
|
coverageDirectory: '../../../coverage/libs/shared/ws',
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.(ts|mjs|js|html)$': [
|
'^.+\\.(ts|mjs|js|html)$': [
|
||||||
'jest-preset-angular',
|
'jest-preset-angular',
|
||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "stats-ws",
|
"name": "ws",
|
||||||
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||||
"sourceRoot": "libs/shared/stats-ws/src",
|
"sourceRoot": "libs/shared/ws/src",
|
||||||
"prefix": "frontend",
|
"prefix": "frontend",
|
||||||
"projectType": "library",
|
"projectType": "library",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
@ -10,7 +10,7 @@
|
|||||||
"executor": "@nx/jest:jest",
|
"executor": "@nx/jest:jest",
|
||||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||||
"options": {
|
"options": {
|
||||||
"jestConfig": "libs/shared/stats-ws/jest.config.ts"
|
"jestConfig": "libs/shared/ws/jest.config.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lint": {
|
"lint": {
|
||||||
@ -11,7 +11,7 @@ import {
|
|||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class StatsWsService {
|
export class WsService {
|
||||||
readonly containers = signal<ContainerInfo[]>([]);
|
readonly containers = signal<ContainerInfo[]>([]);
|
||||||
readonly containerStats = signal<Record<string, ContainerStats>>({});
|
readonly containerStats = signal<Record<string, ContainerStats>>({});
|
||||||
readonly containerDetail = signal<PayloadContainerDetail | null>(null);
|
readonly containerDetail = signal<PayloadContainerDetail | null>(null);
|
||||||
7
frontend/libs/web/container/README.md
Normal file
7
frontend/libs/web/container/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# container
|
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev).
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `nx test container` to execute the unit tests.
|
||||||
34
frontend/libs/web/container/eslint.config.mjs
Normal file
34
frontend/libs/web/container/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/container/jest.config.ts
Normal file
21
frontend/libs/web/container/jest.config.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export default {
|
||||||
|
displayName: 'container',
|
||||||
|
preset: '../../../jest.preset.js',
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
||||||
|
coverageDirectory: '../../../coverage/libs/web/container',
|
||||||
|
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/container/project.json
Normal file
20
frontend/libs/web/container/project.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "container",
|
||||||
|
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "libs/web/container/src",
|
||||||
|
"prefix": "lib",
|
||||||
|
"projectType": "library",
|
||||||
|
"tags": [],
|
||||||
|
"targets": {
|
||||||
|
"test": {
|
||||||
|
"executor": "@nx/jest:jest",
|
||||||
|
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||||
|
"options": {
|
||||||
|
"jestConfig": "libs/web/container/jest.config.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nx/eslint:lint"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
frontend/libs/web/container/src/index.ts
Normal file
1
frontend/libs/web/container/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './lib/container-details.component';
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { Component, inject, OnInit } from '@angular/core';
|
||||||
|
import { WsService } from '@frontend/shared/ws';
|
||||||
|
import { queryParam } from '@mmstack/router-core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'frontend-container-details',
|
||||||
|
imports: [],
|
||||||
|
template: `Container ID: {{ containerId() }}`,
|
||||||
|
styles: [``],
|
||||||
|
})
|
||||||
|
export class ContainerDetailsComponent implements OnInit {
|
||||||
|
private readonly service = inject(WsService);
|
||||||
|
protected readonly containerId = queryParam('id');
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
const id = this.containerId();
|
||||||
|
if (id) {
|
||||||
|
this.service.initContainerDetail(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
6
frontend/libs/web/container/src/test-setup.ts
Normal file
6
frontend/libs/web/container/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/container/tsconfig.json
Normal file
28
frontend/libs/web/container/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/container/tsconfig.lib.json
Normal file
17
frontend/libs/web/container/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/container/tsconfig.spec.json
Normal file
16
frontend/libs/web/container/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"
|
||||||
|
]
|
||||||
|
}
|
||||||
211
frontend/libs/web/containers/src/lib/container-card.component.ts
Normal file
211
frontend/libs/web/containers/src/lib/container-card.component.ts
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
import { NgClass } from '@angular/common';
|
||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { MatChipsModule } from '@angular/material/chips';
|
||||||
|
import { MatDividerModule } from '@angular/material/divider';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'frontend-container-card',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
MatCardModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatChipsModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatDividerModule,
|
||||||
|
MatTooltipModule,
|
||||||
|
NgClass,
|
||||||
|
],
|
||||||
|
template: `
|
||||||
|
<mat-card
|
||||||
|
class="container-card"
|
||||||
|
(click)="containerClick.emit(container.id)"
|
||||||
|
>
|
||||||
|
<mat-card-header>
|
||||||
|
<div mat-card-avatar class="avatar-container">
|
||||||
|
<mat-icon
|
||||||
|
class="status-icon"
|
||||||
|
[ngClass]="getStateClass(container.state)"
|
||||||
|
[matTooltip]="container.state"
|
||||||
|
>
|
||||||
|
{{ getStateIcon(container.state) }}
|
||||||
|
</mat-icon>
|
||||||
|
</div>
|
||||||
|
<mat-card-title class="mat-headline-6">{{
|
||||||
|
container.name
|
||||||
|
}}</mat-card-title>
|
||||||
|
<mat-card-subtitle class="mat-subtitle-2">{{
|
||||||
|
container.image
|
||||||
|
}}</mat-card-subtitle>
|
||||||
|
</mat-card-header>
|
||||||
|
|
||||||
|
<mat-card-content class="card-content">
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="label">ID:</span>
|
||||||
|
<span class="value monospace">{{
|
||||||
|
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-divider></mat-divider>
|
||||||
|
|
||||||
|
<mat-card-actions>
|
||||||
|
<button
|
||||||
|
mat-stroked-button
|
||||||
|
[disabled]="true"
|
||||||
|
[color]="container.state === 'running' ? 'warn' : 'primary'"
|
||||||
|
class="action-button"
|
||||||
|
(click)="onActionClick($event, container.id, container.state)"
|
||||||
|
>
|
||||||
|
<mat-icon>{{
|
||||||
|
container.state === 'running' ? 'stop' : 'play_arrow'
|
||||||
|
}}</mat-icon>
|
||||||
|
{{ container.state === 'running' ? 'STOP' : 'START' }}
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
|
`,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
.container-card {
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.container-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.04);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
.status-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
padding: 16px 16px 8px;
|
||||||
|
}
|
||||||
|
.info-row {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--mat-text-secondary-color);
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
.value {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.monospace {
|
||||||
|
font-family: 'Roboto Mono', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-running {
|
||||||
|
color: var(--mat-green-500);
|
||||||
|
}
|
||||||
|
.state-exited,
|
||||||
|
.state-stopped {
|
||||||
|
color: var(--mat-red-500);
|
||||||
|
}
|
||||||
|
.state-created,
|
||||||
|
.state-paused {
|
||||||
|
color: var(--mat-amber-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-chip-running {
|
||||||
|
background-color: var(--mat-green-50) !important;
|
||||||
|
color: var(--mat-green-700) !important;
|
||||||
|
}
|
||||||
|
.status-chip-exited,
|
||||||
|
.status-chip-stopped {
|
||||||
|
background-color: var(--mat-red-50) !important;
|
||||||
|
color: var(--mat-red-700) !important;
|
||||||
|
}
|
||||||
|
.status-chip-created,
|
||||||
|
.status-chip-paused {
|
||||||
|
background-color: var(--mat-amber-50) !important;
|
||||||
|
color: var(--mat-amber-700) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card-actions {
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.action-button {
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 599px) {
|
||||||
|
mat-card-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.action-button {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ContainerCardComponent {
|
||||||
|
@Input() container: any;
|
||||||
|
@Output() containerClick = new EventEmitter<string>();
|
||||||
|
@Output() actionClick = new EventEmitter<{ id: string; state: string }>();
|
||||||
|
|
||||||
|
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 '';
|
||||||
|
}
|
||||||
|
|
||||||
|
onActionClick(event: Event, id: string, state: string) {
|
||||||
|
event.stopPropagation();
|
||||||
|
this.actionClick.emit({ id, state });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -8,7 +8,6 @@ import { ContainersComponent } from './containers.component';
|
|||||||
imports: [ContainersComponent, MatToolbarModule, MatIconModule],
|
imports: [ContainersComponent, MatToolbarModule, MatIconModule],
|
||||||
template: `
|
template: `
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<p class="description">View and manage your Docker containers here.</p>
|
|
||||||
<frontend-containers />
|
<frontend-containers />
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
@ -27,4 +26,3 @@ import { ContainersComponent } from './containers.component';
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ContainersComponentShell {}
|
export class ContainersComponentShell {}
|
||||||
|
|
||||||
|
|||||||
@ -1,62 +1,20 @@
|
|||||||
import { NgClass } from '@angular/common';
|
|
||||||
import { Component, inject, OnInit } from '@angular/core';
|
import { Component, inject, OnInit } from '@angular/core';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { Router } from '@angular/router';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { WsService } from '@frontend/shared/ws';
|
||||||
import { MatChipsModule } from '@angular/material/chips';
|
import { ContainerCardComponent } from './container-card.component';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
|
||||||
import { StatsWsService } from '@frontend/shared/stats-ws';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'frontend-containers',
|
selector: 'frontend-containers',
|
||||||
imports: [
|
standalone: true,
|
||||||
MatCardModule,
|
imports: [ContainerCardComponent],
|
||||||
MatIconModule,
|
|
||||||
MatChipsModule,
|
|
||||||
MatButtonModule,
|
|
||||||
NgClass,
|
|
||||||
],
|
|
||||||
template: `
|
template: `
|
||||||
<div class="content-container">
|
<div class="content-container">
|
||||||
@for (container of containers(); track container.id) {
|
@for (container of containers(); track container.id) {
|
||||||
<mat-card class="container-card">
|
<frontend-container-card
|
||||||
<mat-card-header>
|
[container]="container"
|
||||||
<mat-icon
|
(containerClick)="onContainerClick($event)"
|
||||||
mat-card-avatar
|
(actionClick)="onContainerAction($event)"
|
||||||
[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>
|
</div>
|
||||||
`,
|
`,
|
||||||
@ -64,111 +22,38 @@ import { StatsWsService } from '@frontend/shared/stats-ws';
|
|||||||
`
|
`
|
||||||
.content-container {
|
.content-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||||
gap: 16px;
|
gap: 24px;
|
||||||
margin-top: 16px;
|
padding: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-card {
|
@media (max-width: 599px) {
|
||||||
transition:
|
.content-container {
|
||||||
transform 0.2s,
|
padding: 16px;
|
||||||
box-shadow 0.2s;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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 {
|
export class ContainersComponent implements OnInit {
|
||||||
private readonly service = inject(StatsWsService);
|
private readonly service = inject(WsService);
|
||||||
|
private readonly router = inject(Router);
|
||||||
protected containers = this.service.containers;
|
protected containers = this.service.containers;
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.service.initContainers();
|
this.service.initContainers();
|
||||||
}
|
}
|
||||||
|
|
||||||
getStateIcon(state: string): string {
|
onContainerClick(id: string): void {
|
||||||
switch (state) {
|
this.router.navigate(['/container'], {
|
||||||
case 'running':
|
queryParams: { id },
|
||||||
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 {
|
onContainerAction(action: { id: string; state: string }): void {
|
||||||
return `state-${state.toLowerCase()}`;
|
console.log(
|
||||||
}
|
`Container action clicked: ${action.id}, State: ${action.state}`
|
||||||
|
);
|
||||||
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 '';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
840
frontend/package-lock.json
generated
840
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -9,28 +9,29 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/cdk": "^20.0.1",
|
"@angular/cdk": "20.0.2",
|
||||||
"@angular/common": "20.0.0",
|
"@angular/common": "20.0.2",
|
||||||
"@angular/compiler": "20.0.0",
|
"@angular/compiler": "20.0.2",
|
||||||
"@angular/core": "20.0.0",
|
"@angular/core": "20.0.2",
|
||||||
"@angular/forms": "20.0.0",
|
"@angular/forms": "20.0.2",
|
||||||
"@angular/material": "^20.0.1",
|
"@angular/material": "20.0.2",
|
||||||
"@angular/platform-browser": "20.0.0",
|
"@angular/platform-browser": "20.0.2",
|
||||||
"@angular/platform-browser-dynamic": "20.0.0",
|
"@angular/platform-browser-dynamic": "20.0.2",
|
||||||
"@angular/router": "20.0.0",
|
"@angular/router": "20.0.2",
|
||||||
"@mmstack/form-material": "^19.2.0",
|
"@mmstack/form-material": "19.2.2",
|
||||||
"@mmstack/primitives": "^19.2.3",
|
"@mmstack/primitives": "19.2.3",
|
||||||
"@mmstack/resource": "^19.2.0",
|
"@mmstack/resource": "19.2.0",
|
||||||
|
"@mmstack/router-core": "^19.3.0",
|
||||||
"rxjs": "7.8.2",
|
"rxjs": "7.8.2",
|
||||||
"zone.js": "0.15.1"
|
"zone.js": "0.15.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "20.0.0",
|
"@angular-devkit/build-angular": "20.0.1",
|
||||||
"@angular-devkit/core": "20.0.0",
|
"@angular-devkit/core": "20.0.1",
|
||||||
"@angular-devkit/schematics": "20.0.0",
|
"@angular-devkit/schematics": "20.0.1",
|
||||||
"@angular/cli": "20.0.0",
|
"@angular/cli": "20.0.1",
|
||||||
"@angular/compiler-cli": "20.0.0",
|
"@angular/compiler-cli": "20.0.2",
|
||||||
"@angular/language-service": "20.0.0",
|
"@angular/language-service": "20.0.2",
|
||||||
"@eslint/js": "9.28.0",
|
"@eslint/js": "9.28.0",
|
||||||
"@nx/angular": "21.1.3",
|
"@nx/angular": "21.1.3",
|
||||||
"@nx/eslint": "21.1.3",
|
"@nx/eslint": "21.1.3",
|
||||||
@ -39,14 +40,14 @@
|
|||||||
"@nx/js": "21.1.3",
|
"@nx/js": "21.1.3",
|
||||||
"@nx/web": "21.1.3",
|
"@nx/web": "21.1.3",
|
||||||
"@nx/workspace": "21.1.3",
|
"@nx/workspace": "21.1.3",
|
||||||
"@schematics/angular": "20.0.0",
|
"@schematics/angular": "20.0.1",
|
||||||
"@swc-node/register": "1.10.10",
|
"@swc-node/register": "1.10.10",
|
||||||
"@swc/core": "1.11.29",
|
"@swc/core": "1.11.31",
|
||||||
"@swc/helpers": "0.5.17",
|
"@swc/helpers": "0.5.17",
|
||||||
"@types/jest": "29.5.14",
|
"@types/jest": "29.5.14",
|
||||||
"@types/node": "22.15.29",
|
"@types/node": "22.15.30",
|
||||||
"@typescript-eslint/utils": "8.33.1",
|
"@typescript-eslint/utils": "8.33.1",
|
||||||
"angular-eslint": "19.7.0",
|
"angular-eslint": "20.0.0",
|
||||||
"eslint": "9.28.0",
|
"eslint": "9.28.0",
|
||||||
"eslint-config-prettier": "10.1.5",
|
"eslint-config-prettier": "10.1.5",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
|
|||||||
@ -1,15 +1,16 @@
|
|||||||
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
|
||||||
import { provideRouter } from '@angular/router';
|
|
||||||
import { appRoutes } from './app.routes';
|
|
||||||
import { provideHttpClient } from '@angular/common/http';
|
import { provideHttpClient } from '@angular/common/http';
|
||||||
|
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
||||||
|
import { provideRouter, withPreloading } from '@angular/router';
|
||||||
import { ENVIRONMENT } from '@frontend/shared/environment';
|
import { ENVIRONMENT } from '@frontend/shared/environment';
|
||||||
|
import { PreloadStrategy } from '@mmstack/router-core';
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
|
import { appRoutes } from './app.routes';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||||
provideHttpClient(),
|
provideHttpClient(),
|
||||||
provideRouter(appRoutes),
|
provideRouter(appRoutes, withPreloading(PreloadStrategy)),
|
||||||
{ provide: ENVIRONMENT, useValue: environment }
|
{ provide: ENVIRONMENT, useValue: environment },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,7 +4,16 @@ export const appRoutes: Route[] = [
|
|||||||
{
|
{
|
||||||
path: 'containers',
|
path: 'containers',
|
||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('@frontend/web/container').then((m) => m.ContainersComponentShell),
|
import('@frontend/web/containers').then(
|
||||||
|
(m) => m.ContainersComponentShell
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'container',
|
||||||
|
loadComponent: () =>
|
||||||
|
import('@frontend/web/container').then(
|
||||||
|
(m) => m.ContainerDetailsComponent
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '**',
|
path: '**',
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
import { Environment } from '@frontend/shared/environment';
|
|
||||||
|
|
||||||
export const environment: Environment = {
|
|
||||||
// Change this to your production WebSocket server URL
|
|
||||||
wsBaseUrl: 'wss://your-production-server.com',
|
|
||||||
production: true,
|
|
||||||
apis: {
|
|
||||||
containers: '/ws/containers',
|
|
||||||
container: '/ws/container',
|
|
||||||
stats: '/ws/stats',
|
|
||||||
combined: '/ws/combined'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,13 +1,14 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>frontend</title>
|
<title>frontend</title>
|
||||||
<base href="/" />
|
<base href="/" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||||
</head>
|
<script src="env.js"></script>
|
||||||
<body>
|
</head>
|
||||||
<app-root></app-root>
|
<body>
|
||||||
</body>
|
<app-root></app-root>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -7,7 +7,6 @@ body {
|
|||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: Roboto, 'Helvetica Neue', sans-serif;
|
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
color: rgba(0, 0, 0, 0.87);
|
color: rgba(0, 0, 0, 0.87);
|
||||||
transition:
|
transition:
|
||||||
@ -20,7 +19,6 @@ body.dark-theme {
|
|||||||
color: rgba(255, 255, 255, 0.87);
|
color: rgba(255, 255, 255, 0.87);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add this for the mat-card styling
|
|
||||||
.container {
|
.container {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
transition:
|
transition:
|
||||||
@ -28,7 +26,6 @@ body.dark-theme {
|
|||||||
color 0.3s ease;
|
color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure mat-card content changes color appropriately
|
|
||||||
body.dark-theme .mat-mdc-card {
|
body.dark-theme .mat-mdc-card {
|
||||||
--mdc-elevated-card-container-color: #424242;
|
--mdc-elevated-card-container-color: #424242;
|
||||||
color: rgba(255, 255, 255, 0.87);
|
color: rgba(255, 255, 255, 0.87);
|
||||||
|
|||||||
@ -17,11 +17,12 @@
|
|||||||
"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/navbar": ["libs/shared/navbar/src/index.ts"],
|
||||||
"@frontend/shared/stats-ws": ["libs/shared/stats-ws/src/index.ts"],
|
|
||||||
"@frontend/shared/theme-toggle": [
|
"@frontend/shared/theme-toggle": [
|
||||||
"libs/shared/theme-toggle/src/index.ts"
|
"libs/shared/theme-toggle/src/index.ts"
|
||||||
],
|
],
|
||||||
"@frontend/web/container": ["libs/web/containers/src/index.ts"]
|
"@frontend/shared/ws": ["libs/shared/ws/src/index.ts"],
|
||||||
|
"@frontend/web/container": ["libs/web/container/src/index.ts"],
|
||||||
|
"@frontend/web/containers": ["libs/web/containers/src/index.ts"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "tmp"]
|
"exclude": ["node_modules", "tmp"]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user