nuget reorganization

This commit is contained in:
master
2025-11-18 23:45:25 +02:00
parent 77cee6a209
commit d3ecd7f8e6
7712 changed files with 13963 additions and 10007504 deletions

View File

@@ -0,0 +1,27 @@
# Helm Readiness & Probes
This app serves static health endpoints for platform probes:
- `/assets/health/liveness.json`
- `/assets/health/readiness.json`
- `/assets/health/version.json`
These are packaged with the Angular build. Configure Helm/Nginx to route the probes directly to the web pod.
## Suggested Helm values
```yaml
livenessProbe:
httpGet:
path: /assets/health/liveness.json
port: http
readinessProbe:
httpGet:
path: /assets/health/readiness.json
port: http
```
## Updating
- Edit the JSON under `src/assets/health/*.json` for environment-specific readiness details.
- Run `npm run build` (or CI pipeline) to bake the files into the image.

View File

@@ -1,6 +1,16 @@
<div class="app-shell">
<header class="app-header">
<div class="app-brand">StellaOps Dashboard</div>
<div class="app-shell">
<section
class="quickstart-banner"
*ngIf="quickstartEnabled()"
aria-label="Quickstart mode active"
>
<div>
QUICKSTART MODE is enabled. Configuration and data shown are for demo/offline
setup. See the <a routerLink="/welcome">welcome</a> page for details.
</div>
</section>
<header class="app-header">
<div class="app-brand">StellaOps Dashboard</div>
<nav class="app-nav">
<a routerLink="/console/profile" routerLinkActive="active">
Console Profile
@@ -11,10 +21,13 @@
<a routerLink="/scans/scan-verified-001" routerLinkActive="active">
Scan Detail
</a>
<a routerLink="/notify" routerLinkActive="active">
Notify
</a>
</nav>
<a routerLink="/notify" routerLinkActive="active">
Notify
</a>
<a routerLink="/welcome" routerLinkActive="active">
Welcome
</a>
</nav>
<div class="app-auth">
<ng-container *ngIf="isAuthenticated(); else signIn">
<span class="app-user" aria-live="polite">{{ displayName() }}</span>

View File

@@ -7,11 +7,25 @@
background-color: #f8fafc;
}
.app-shell {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.app-shell {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.quickstart-banner {
background: #fef3c7;
color: #92400e;
padding: 0.75rem 1.5rem;
font-size: 0.9rem;
border-bottom: 1px solid #fcd34d;
a {
color: inherit;
font-weight: 600;
text-decoration: underline;
}
}
.app-header {
display: flex;

View File

@@ -1,34 +1,36 @@
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
computed,
inject,
} from '@angular/core';
import { Router, RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { AuthorityAuthService } from './core/auth/authority-auth.service';
import { AuthSessionStore } from './core/auth/auth-session.store';
import { ConsoleSessionStore } from './core/console/console-session.store';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, RouterOutlet, RouterLink, RouterLinkActive],
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
computed,
inject,
} from '@angular/core';
import { Router, RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { AuthorityAuthService } from './core/auth/authority-auth.service';
import { AuthSessionStore } from './core/auth/auth-session.store';
import { ConsoleSessionStore } from './core/console/console-session.store';
import { AppConfigService } from './core/config/app-config.service';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, RouterOutlet, RouterLink, RouterLinkActive],
templateUrl: './app.component.html',
styleUrl: './app.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent {
private readonly router = inject(Router);
private readonly auth = inject(AuthorityAuthService);
private readonly sessionStore = inject(AuthSessionStore);
private readonly consoleStore = inject(ConsoleSessionStore);
readonly status = this.sessionStore.status;
readonly identity = this.sessionStore.identity;
readonly subjectHint = this.sessionStore.subjectHint;
readonly isAuthenticated = this.sessionStore.isAuthenticated;
private readonly router = inject(Router);
private readonly auth = inject(AuthorityAuthService);
private readonly sessionStore = inject(AuthSessionStore);
private readonly consoleStore = inject(ConsoleSessionStore);
private readonly config = inject(AppConfigService);
readonly status = this.sessionStore.status;
readonly identity = this.sessionStore.identity;
readonly subjectHint = this.sessionStore.subjectHint;
readonly isAuthenticated = this.sessionStore.isAuthenticated;
readonly activeTenant = this.consoleStore.selectedTenantId;
readonly freshAuthSummary = computed(() => {
const token = this.consoleStore.tokenInfo();
@@ -49,14 +51,18 @@ export class AppComponent {
if (identity?.email) {
return identity.email;
}
const hint = this.subjectHint();
return hint ?? 'anonymous';
});
onSignIn(): void {
const returnUrl = this.router.url === '/' ? undefined : this.router.url;
void this.auth.beginLogin(returnUrl);
}
const hint = this.subjectHint();
return hint ?? 'anonymous';
});
readonly quickstartEnabled = computed(
() => this.config.config.quickstartMode ?? false
);
onSignIn(): void {
const returnUrl = this.router.url === '/' ? undefined : this.router.url;
void this.auth.beginLogin(returnUrl);
}
onSignOut(): void {
void this.auth.logout();

View File

@@ -15,19 +15,26 @@ export const routes: Routes = [
(m) => m.TrivyDbSettingsPageComponent
),
},
{
path: 'scans/:scanId',
loadComponent: () =>
import('./features/scans/scan-detail-page.component').then(
(m) => m.ScanDetailPageComponent
),
},
{
path: 'notify',
loadComponent: () =>
import('./features/notify/notify-panel.component').then(
(m) => m.NotifyPanelComponent
),
{
path: 'scans/:scanId',
loadComponent: () =>
import('./features/scans/scan-detail-page.component').then(
(m) => m.ScanDetailPageComponent
),
},
{
path: 'welcome',
loadComponent: () =>
import('./features/welcome/welcome-page.component').then(
(m) => m.WelcomePageComponent
),
},
{
path: 'notify',
loadComponent: () =>
import('./features/notify/notify-panel.component').then(
(m) => m.NotifyPanelComponent
),
},
{
path: 'auth/callback',

View File

@@ -35,15 +35,29 @@ export interface ApiBaseUrlConfig {
readonly scheduler?: string;
}
export interface TelemetryConfig {
readonly otlpEndpoint?: string;
readonly sampleRate?: number;
}
export interface AppConfig {
readonly authority: AuthorityConfig;
readonly apiBaseUrls: ApiBaseUrlConfig;
readonly telemetry?: TelemetryConfig;
}
export const APP_CONFIG = new InjectionToken<AppConfig>('STELLAOPS_APP_CONFIG');
export interface TelemetryConfig {
readonly otlpEndpoint?: string;
readonly sampleRate?: number;
}
export interface WelcomeConfig {
readonly title?: string;
readonly message?: string;
readonly docsUrl?: string;
}
export interface AppConfig {
readonly authority: AuthorityConfig;
readonly apiBaseUrls: ApiBaseUrlConfig;
readonly telemetry?: TelemetryConfig;
/**
* Enables quickstart banner and relaxed UX defaults for demos.
*/
readonly quickstartMode?: boolean;
/**
* Optional welcome metadata surfaced at /welcome for config discovery.
*/
readonly welcome?: WelcomeConfig;
}
export const APP_CONFIG = new InjectionToken<AppConfig>('STELLAOPS_APP_CONFIG');

View File

@@ -15,14 +15,15 @@ import {
DPoPAlgorithm,
} from './app-config.model';
const DEFAULT_CONFIG_URL = '/config.json';
const DEFAULT_DPOP_ALG: DPoPAlgorithm = 'ES256';
const DEFAULT_REFRESH_LEEWAY_SECONDS = 60;
@Injectable({
providedIn: 'root',
})
export class AppConfigService {
const DEFAULT_CONFIG_URL = '/config.json';
const DEFAULT_DPOP_ALG: DPoPAlgorithm = 'ES256';
const DEFAULT_REFRESH_LEEWAY_SECONDS = 60;
const DEFAULT_QUICKSTART = false;
@Injectable({
providedIn: 'root',
})
export class AppConfigService {
private readonly configSignal = signal<AppConfig | null>(null);
private readonly authoritySignal = computed<AuthorityConfig | null>(() => {
const config = this.configSignal();
@@ -80,20 +81,21 @@ export class AppConfigService {
return response;
}
private normalizeConfig(config: AppConfig): AppConfig {
const authority = {
...config.authority,
dpopAlgorithms:
config.authority.dpopAlgorithms?.length ?? 0
private normalizeConfig(config: AppConfig): AppConfig {
const authority = {
...config.authority,
dpopAlgorithms:
config.authority.dpopAlgorithms?.length ?? 0
? config.authority.dpopAlgorithms
: [DEFAULT_DPOP_ALG],
refreshLeewaySeconds:
config.authority.refreshLeewaySeconds ?? DEFAULT_REFRESH_LEEWAY_SECONDS,
};
return {
...config,
authority,
};
}
}
};
return {
...config,
authority,
quickstartMode: config.quickstartMode ?? DEFAULT_QUICKSTART,
};
}
}

View File

@@ -0,0 +1,123 @@
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
computed,
inject,
} from '@angular/core';
import { AppConfigService } from '../../core/config/app-config.service';
@Component({
standalone: true,
selector: 'app-welcome-page',
imports: [CommonModule],
template: `
<section class="welcome-card">
<h1>{{ title() }}</h1>
<p class="message">{{ message() }}</p>
<dl class="config-grid">
<div>
<dt>Quickstart mode</dt>
<dd>{{ quickstartEnabled() ? 'Enabled' : 'Disabled' }}</dd>
</div>
<div>
<dt>Authority</dt>
<dd>{{ config().authority.issuer }}</dd>
</div>
<div>
<dt>Policy API</dt>
<dd>{{ config().apiBaseUrls.policy }}</dd>
</div>
<div>
<dt>Scanner API</dt>
<dd>{{ config().apiBaseUrls.scanner }}</dd>
</div>
</dl>
<a
*ngIf="docsUrl() as docs"
class="docs-link"
[href]="docs"
rel="noreferrer"
target="_blank"
>
View deployment guide
</a>
</section>
`,
styles: [
`
:host {
display: block;
max-width: 720px;
margin: 0 auto;
}
.welcome-card {
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 12px;
padding: 1.5rem;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.08);
}
h1 {
margin: 0 0 0.5rem;
font-size: 1.5rem;
}
.message {
margin: 0 0 1rem;
color: #475569;
}
.config-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 0.75rem;
margin: 1rem 0;
}
dt {
font-size: 0.85rem;
color: #334155;
margin-bottom: 0.1rem;
}
dd {
margin: 0;
font-weight: 600;
color: #0f172a;
word-break: break-all;
}
.docs-link {
display: inline-block;
margin-top: 0.5rem;
color: #4338ca;
font-weight: 600;
text-decoration: none;
}
`,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class WelcomePageComponent {
private readonly configService = inject(AppConfigService);
readonly config = computed(() => this.configService.config);
readonly quickstartEnabled = computed(
() => this.config().quickstartMode ?? false
);
readonly title = computed(
() => this.config().welcome?.title ?? 'Welcome to StellaOps'
);
readonly message = computed(
() =>
this.config().welcome?.message ??
'This page surfaces safe deployment configuration for operators.'
);
readonly docsUrl = computed(() => this.config().welcome?.docsUrl);
}

View File

@@ -0,0 +1,4 @@
{
"status": "OK",
"timestamp": "1970-01-01T00:00:00Z"
}

View File

@@ -0,0 +1,8 @@
{
"status": "OK",
"dependencies": {
"api": "UNKNOWN",
"telemetry": "DISABLED"
},
"timestamp": "1970-01-01T00:00:00Z"
}

View File

@@ -0,0 +1,5 @@
{
"version": "0.0.0",
"commit": "local",
"builtAt": "1970-01-01T00:00:00Z"
}

View File

@@ -12,15 +12,21 @@
"dpopAlgorithms": ["ES256"],
"refreshLeewaySeconds": 60
},
"apiBaseUrls": {
"authority": "https://authority.local",
"scanner": "https://scanner.local",
"policy": "https://scanner.local",
"concelier": "https://concelier.local",
"attestor": "https://attestor.local"
},
"telemetry": {
"otlpEndpoint": "http://localhost:4318/v1/traces",
"sampleRate": 0.1
}
}
"apiBaseUrls": {
"authority": "https://authority.local",
"scanner": "https://scanner.local",
"policy": "https://scanner.local",
"concelier": "https://concelier.local",
"attestor": "https://attestor.local"
},
"telemetry": {
"otlpEndpoint": "http://localhost:4318/v1/traces",
"sampleRate": 0.1
},
"quickstartMode": true,
"welcome": {
"title": "StellaOps Web Quickstart",
"message": "Local demo configuration. Replace endpoints and disable quickstart for production.",
"docsUrl": "https://docs.stellaops.example/quickstart"
}
}

View File

@@ -12,15 +12,21 @@
"dpopAlgorithms": ["ES256"],
"refreshLeewaySeconds": 60
},
"apiBaseUrls": {
"authority": "https://authority.example.dev",
"scanner": "https://scanner.example.dev",
"policy": "https://scanner.example.dev",
"concelier": "https://concelier.example.dev",
"attestor": "https://attestor.example.dev"
},
"telemetry": {
"otlpEndpoint": "",
"sampleRate": 0
}
}
"apiBaseUrls": {
"authority": "https://authority.example.dev",
"scanner": "https://scanner.example.dev",
"policy": "https://scanner.example.dev",
"concelier": "https://concelier.example.dev",
"attestor": "https://attestor.example.dev"
},
"telemetry": {
"otlpEndpoint": "",
"sampleRate": 0
},
"quickstartMode": true,
"welcome": {
"title": "StellaOps Web Quickstart",
"message": "You are viewing a demo configuration. Replace endpoints with your deployment values and disable quickstart when promoting to production.",
"docsUrl": "https://docs.stellaops.example/quickstart"
}
}