more features checks. setup improvements
This commit is contained in:
@@ -9,6 +9,9 @@
|
||||
}
|
||||
@if (useShellLayout()) {
|
||||
<app-shell></app-shell>
|
||||
} @else if (isFullPageRoute()) {
|
||||
<!-- Full-page routes (setup wizard): no app chrome, just the component -->
|
||||
<router-outlet />
|
||||
} @else {
|
||||
<header class="app-header">
|
||||
<a class="app-brand" routerLink="/">
|
||||
|
||||
@@ -114,7 +114,9 @@ export class AppComponent {
|
||||
startWith(this.router.url.split('?')[0])
|
||||
);
|
||||
|
||||
private readonly currentUrl = toSignal(this.currentUrl$, { initialValue: '/' });
|
||||
private readonly currentUrl = toSignal(this.currentUrl$, {
|
||||
initialValue: (typeof window !== 'undefined' ? window.location.pathname : '/'),
|
||||
});
|
||||
|
||||
readonly useShellLayout = computed(() => {
|
||||
const url = this.currentUrl();
|
||||
@@ -134,6 +136,12 @@ export class AppComponent {
|
||||
return url.split('/').filter(s => s).length > 0;
|
||||
});
|
||||
|
||||
/** Setup wizard gets a completely chrome-free viewport. */
|
||||
readonly isFullPageRoute = computed(() => {
|
||||
const url = this.currentUrl();
|
||||
return url === '/setup' || url.startsWith('/setup/');
|
||||
});
|
||||
|
||||
/** Hide navigation on setup/auth pages and when not authenticated. */
|
||||
readonly showNavigation = computed(() => {
|
||||
const url = this.currentUrl();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -67,25 +67,7 @@ import {
|
||||
}
|
||||
</header>
|
||||
|
||||
<!-- Status Banner -->
|
||||
@if (step().status === 'completed') {
|
||||
<div class="status-banner success">
|
||||
<span class="status-icon">OK</span>
|
||||
<span>This step has been completed successfully.</span>
|
||||
</div>
|
||||
}
|
||||
@if (step().status === 'skipped') {
|
||||
<div class="status-banner skipped">
|
||||
<span class="status-icon">--</span>
|
||||
<span>This step was skipped.</span>
|
||||
</div>
|
||||
}
|
||||
@if (step().status === 'failed') {
|
||||
<div class="status-banner error">
|
||||
<span class="status-icon">!</span>
|
||||
<span>{{ step().error ?? 'Step execution failed' }}</span>
|
||||
</div>
|
||||
}
|
||||
<!-- Status is shown in the accordion row and inline test result; no banner needed -->
|
||||
|
||||
<!-- Dynamic Form Content -->
|
||||
<div class="form-container">
|
||||
@@ -168,21 +150,26 @@ import {
|
||||
</section>
|
||||
}
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="step-actions">
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
(click)="onTest()"
|
||||
[disabled]="executing()">
|
||||
{{ executing() ? 'Testing...' : 'Test Connection' }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
(click)="onExecute()"
|
||||
[disabled]="executing()">
|
||||
{{ executing() ? 'Configuring...' : (dryRunMode() ? 'Validate Configuration' : 'Apply Configuration') }}
|
||||
</button>
|
||||
</div>
|
||||
<!-- Action Buttons (hidden when form has its own inline button) -->
|
||||
@if (step().id !== 'database') {
|
||||
<div class="step-actions">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
(click)="onTest()"
|
||||
[disabled]="executing()">
|
||||
{{ executing() ? 'Validating...' : 'Validate & Test' }}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Inline test result (replaces on each click, with slide-in animation) -->
|
||||
@if (testResult(); as tr) {
|
||||
<div class="test-result" [class.test-result--ok]="tr.success"
|
||||
[class.test-result--fail]="!tr.success">
|
||||
<span class="test-result-dot"></span>
|
||||
<span class="test-result-msg">{{ tr.message }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Authority Form Template -->
|
||||
<ng-template #authorityForm>
|
||||
@@ -275,8 +262,9 @@ import {
|
||||
<input
|
||||
id="users-superuser-password"
|
||||
type="password"
|
||||
[value]="getConfigValue('users.superuser.password')"
|
||||
[value]="getConfigValue('users.superuser.password') || 'Admin@Stella1'"
|
||||
(input)="onInputChange('users.superuser.password', $event)"
|
||||
placeholder="Admin@Stella1"
|
||||
/>
|
||||
<span class="help-text">Must meet password policy requirements.</span>
|
||||
</div>
|
||||
@@ -348,8 +336,18 @@ import {
|
||||
<!-- Database Form Template -->
|
||||
<ng-template #databaseForm>
|
||||
<div class="form-section">
|
||||
<h3>PostgreSQL Connection</h3>
|
||||
<p class="section-hint">Enter a connection string or individual connection parameters.</p>
|
||||
<div class="form-section-head">
|
||||
<div>
|
||||
<h3>PostgreSQL Connection</h3>
|
||||
<p class="section-hint">Enter a connection string or individual connection parameters.</p>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary btn-inline-action"
|
||||
(click)="onTest()"
|
||||
[disabled]="executing()">
|
||||
{{ executing() ? 'Validating...' : 'Validate & Test' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="db-connectionString">Connection String</label>
|
||||
@@ -373,9 +371,9 @@ import {
|
||||
<input
|
||||
id="db-host"
|
||||
type="text"
|
||||
[value]="getConfigValue('database.host')"
|
||||
[value]="getConfigValue('database.host') || 'db.stella-ops.local'"
|
||||
(input)="onInputChange('database.host', $event)"
|
||||
placeholder="localhost"
|
||||
placeholder="db.stella-ops.local"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group form-group-small">
|
||||
@@ -389,26 +387,25 @@ import {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="db-database">Database*</label>
|
||||
<input
|
||||
id="db-database"
|
||||
type="text"
|
||||
[value]="getConfigValue('database.database')"
|
||||
(input)="onInputChange('database.database', $event)"
|
||||
placeholder="stellaops"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-row form-row--3col">
|
||||
<div class="form-group">
|
||||
<label for="db-database">Database*</label>
|
||||
<input
|
||||
id="db-database"
|
||||
type="text"
|
||||
[value]="getConfigValue('database.database') || 'stellaops_platform'"
|
||||
(input)="onInputChange('database.database', $event)"
|
||||
placeholder="stellaops_platform"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="db-user">Username*</label>
|
||||
<input
|
||||
id="db-user"
|
||||
type="text"
|
||||
[value]="getConfigValue('database.user')"
|
||||
[value]="getConfigValue('database.user') || 'stellaops'"
|
||||
(input)="onInputChange('database.user', $event)"
|
||||
placeholder="postgres"
|
||||
placeholder="stellaops"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -416,7 +413,7 @@ import {
|
||||
<input
|
||||
id="db-password"
|
||||
type="password"
|
||||
[value]="getConfigValue('database.password')"
|
||||
[value]="getConfigValue('database.password') || 'stellaops'"
|
||||
(input)="onInputChange('database.password', $event)"
|
||||
/>
|
||||
</div>
|
||||
@@ -463,7 +460,7 @@ import {
|
||||
type="text"
|
||||
[value]="getConfigValue('cache.host')"
|
||||
(input)="onInputChange('cache.host', $event)"
|
||||
placeholder="localhost"
|
||||
placeholder="cache.stella-ops.local"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group form-group-small">
|
||||
@@ -1718,7 +1715,7 @@ import {
|
||||
`,
|
||||
styles: [`
|
||||
.step-content {
|
||||
max-width: 700px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.step-header {
|
||||
@@ -1852,7 +1849,7 @@ import {
|
||||
.form-group select:focus,
|
||||
.form-group textarea:focus {
|
||||
outline: none;
|
||||
border-color: #1976d2;
|
||||
border-color: #D4922A;
|
||||
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.1);
|
||||
}
|
||||
|
||||
@@ -1944,12 +1941,12 @@ import {
|
||||
}
|
||||
|
||||
.provider-card:hover {
|
||||
border-color: #1976d2;
|
||||
border-color: #D4922A;
|
||||
}
|
||||
|
||||
.provider-card.selected {
|
||||
border-color: #1976d2;
|
||||
background: #e3f2fd;
|
||||
border-color: #D4922A;
|
||||
background: #FFF9ED;
|
||||
}
|
||||
|
||||
.provider-name {
|
||||
@@ -2019,7 +2016,7 @@ import {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-top-color: #1976d2;
|
||||
border-top-color: #D4922A;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@@ -2049,6 +2046,70 @@ import {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* Inline action button next to form heading */
|
||||
.form-section-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.form-section-head h3 { margin: 0; }
|
||||
.form-section-head .section-hint { margin: 4px 0 0; }
|
||||
.btn-inline-action {
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* Inline test result (static text below validate button) */
|
||||
.test-result {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 14px;
|
||||
margin: 12px 0 4px;
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
animation: test-result-in 300ms cubic-bezier(.4,0,.2,1) both;
|
||||
}
|
||||
@keyframes test-result-in {
|
||||
from { opacity: 0; transform: translateY(-6px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.test-result--ok {
|
||||
background: rgba(34,197,94,.08);
|
||||
border: 1px solid rgba(34,197,94,.25);
|
||||
color: #166534;
|
||||
}
|
||||
.test-result--fail {
|
||||
background: #fef2f2;
|
||||
border: 1px solid #fca5a5;
|
||||
color: #991b1b;
|
||||
}
|
||||
.test-result-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.test-result--ok .test-result-dot { background: #22c55e; }
|
||||
.test-result--fail .test-result-dot { background: #ef4444; }
|
||||
.test-result-msg {
|
||||
font-weight: 600;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 3-column row (Database, Username, Password) */
|
||||
.form-row--3col {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
.form-row--3col .form-group {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border: 1px solid #ddd;
|
||||
@@ -2069,13 +2130,15 @@ import {
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #1976d2;
|
||||
border-color: #1976d2;
|
||||
color: var(--color-text-heading);
|
||||
background: var(--color-brand-primary, #F5A623);
|
||||
border-color: var(--color-brand-primary, #F5A623);
|
||||
color: #fff;
|
||||
box-shadow: 0 2px 8px rgba(245,166,35,.18);
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background: #1565c0;
|
||||
background: var(--color-brand-primary-hover, #E09115);
|
||||
box-shadow: 0 4px 14px rgba(245,166,35,.22);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@@ -2097,11 +2160,11 @@ import {
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background: #e3f2fd;
|
||||
border: 1px solid #90caf9;
|
||||
background: #FFF9ED;
|
||||
border: 1px solid #F5D998;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 24px;
|
||||
color: #1565c0;
|
||||
color: #B07820;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
@@ -2110,7 +2173,7 @@ import {
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: #1976d2;
|
||||
background: #D4922A;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
@@ -2208,9 +2271,9 @@ import {
|
||||
}
|
||||
|
||||
.btn-add-user:hover {
|
||||
border-color: #1976d2;
|
||||
color: #1976d2;
|
||||
background: #e3f2fd;
|
||||
border-color: #D4922A;
|
||||
color: #D4922A;
|
||||
background: #FFF9ED;
|
||||
}
|
||||
|
||||
.provider-config h4 {
|
||||
@@ -2242,8 +2305,8 @@ import {
|
||||
}
|
||||
|
||||
.event-rule-card.enabled {
|
||||
border-color: #1976d2;
|
||||
background: #e3f2fd;
|
||||
border-color: #D4922A;
|
||||
background: #FFF9ED;
|
||||
}
|
||||
|
||||
.rule-header {
|
||||
@@ -2277,8 +2340,8 @@ import {
|
||||
}
|
||||
|
||||
.severity-info {
|
||||
background: #e3f2fd;
|
||||
color: #1565c0;
|
||||
background: #FFF9ED;
|
||||
color: #B07820;
|
||||
}
|
||||
|
||||
.rule-description {
|
||||
@@ -2318,8 +2381,8 @@ import {
|
||||
}
|
||||
|
||||
.source-card.enabled {
|
||||
border-color: #1976d2;
|
||||
background: #e3f2fd;
|
||||
border-color: #D4922A;
|
||||
background: #FFF9ED;
|
||||
}
|
||||
|
||||
.source-header {
|
||||
@@ -2361,8 +2424,8 @@ import {
|
||||
}
|
||||
|
||||
.status-card.checking {
|
||||
background: #e3f2fd;
|
||||
color: #1565c0;
|
||||
background: #FFF9ED;
|
||||
color: #B07820;
|
||||
}
|
||||
|
||||
.status-card.success {
|
||||
@@ -2398,7 +2461,7 @@ import {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-top-color: #1976d2;
|
||||
border-top-color: #D4922A;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@@ -2427,12 +2490,12 @@ import {
|
||||
}
|
||||
|
||||
.pattern-card:hover {
|
||||
border-color: #1976d2;
|
||||
border-color: #D4922A;
|
||||
}
|
||||
|
||||
.pattern-card.selected {
|
||||
border-color: #1976d2;
|
||||
background: #e3f2fd;
|
||||
border-color: #D4922A;
|
||||
background: #FFF9ED;
|
||||
}
|
||||
|
||||
.pattern-name {
|
||||
@@ -2475,7 +2538,7 @@ import {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #1976d2;
|
||||
background: #D4922A;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
font-size: 14px;
|
||||
@@ -2509,9 +2572,9 @@ import {
|
||||
}
|
||||
|
||||
.btn-add-env:hover {
|
||||
border-color: #1976d2;
|
||||
color: #1976d2;
|
||||
background: #e3f2fd;
|
||||
border-color: #D4922A;
|
||||
color: #D4922A;
|
||||
background: #FFF9ED;
|
||||
}
|
||||
|
||||
.promotion-path {
|
||||
@@ -2534,7 +2597,7 @@ import {
|
||||
|
||||
.path-env {
|
||||
padding: 6px 12px;
|
||||
background: #1976d2;
|
||||
background: #D4922A;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
@@ -2599,9 +2662,9 @@ import {
|
||||
}
|
||||
|
||||
.btn-add-agent:hover {
|
||||
border-color: #1976d2;
|
||||
color: #1976d2;
|
||||
background: #e3f2fd;
|
||||
border-color: #D4922A;
|
||||
color: #D4922A;
|
||||
background: #FFF9ED;
|
||||
}
|
||||
|
||||
code {
|
||||
@@ -2626,8 +2689,8 @@ import {
|
||||
}
|
||||
|
||||
.integration-instance.primary {
|
||||
border-color: #1976d2;
|
||||
background: #e3f2fd;
|
||||
border-color: #D4922A;
|
||||
background: #FFF9ED;
|
||||
}
|
||||
|
||||
.instance-header {
|
||||
@@ -2662,11 +2725,11 @@ import {
|
||||
.primary-badge {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: #1976d2;
|
||||
color: #D4922A;
|
||||
background: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #1976d2;
|
||||
border: 1px solid #D4922A;
|
||||
}
|
||||
|
||||
.instance-actions {
|
||||
@@ -2760,9 +2823,9 @@ import {
|
||||
}
|
||||
|
||||
.btn-add-integration:hover {
|
||||
border-color: #1976d2;
|
||||
color: #1976d2;
|
||||
background: #e3f2fd;
|
||||
border-color: #D4922A;
|
||||
color: #D4922A;
|
||||
background: #FFF9ED;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
@@ -2799,6 +2862,9 @@ export class StepContentComponent {
|
||||
/** Whether dry-run mode is enabled */
|
||||
readonly dryRunMode = input(true);
|
||||
|
||||
/** Test connection result (passed from parent) */
|
||||
readonly testResult = input<{ success: boolean; message: string } | null>(null);
|
||||
|
||||
/** Emits configuration changes */
|
||||
readonly configChange = output<{ key: string; value: string }>();
|
||||
|
||||
@@ -2851,6 +2917,45 @@ export class StepContentComponent {
|
||||
readonly newScmProvider = signal<string | null>(null);
|
||||
readonly newNotifyProvider = signal<string | null>(null);
|
||||
|
||||
/** Sensible defaults for local/development setup. */
|
||||
private static readonly LOCAL_DEFAULTS: Record<string, Record<string, string>> = {
|
||||
database: {
|
||||
'database.host': 'db.stella-ops.local',
|
||||
'database.port': '5432',
|
||||
'database.database': 'stellaops_platform',
|
||||
'database.user': 'stellaops',
|
||||
'database.password': 'stellaops',
|
||||
},
|
||||
cache: {
|
||||
'cache.host': 'cache.stella-ops.local',
|
||||
'cache.port': '6379',
|
||||
'cache.database': '0',
|
||||
},
|
||||
authority: {},
|
||||
users: {
|
||||
'users.superuser.username': 'admin',
|
||||
'users.superuser.email': 'admin@stella-ops.local',
|
||||
},
|
||||
telemetry: {
|
||||
'telemetry.otlpEndpoint': 'http://localhost:4317',
|
||||
'telemetry.serviceName': 'stellaops',
|
||||
},
|
||||
};
|
||||
|
||||
/** Emit defaults for the current step if no values are set yet. */
|
||||
private readonly defaultsEffect = effect(() => {
|
||||
const step = this.step();
|
||||
const config = this.configValues();
|
||||
const defaults = StepContentComponent.LOCAL_DEFAULTS[step.id];
|
||||
if (defaults) {
|
||||
for (const [key, value] of Object.entries(defaults)) {
|
||||
if (!config[key]) {
|
||||
this.configChange.emit({ key, value });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Enabled sources (track by source ID)
|
||||
readonly enabledSources = signal<Set<string>>(new Set(['nvd', 'ghsa']));
|
||||
|
||||
|
||||
@@ -27,11 +27,9 @@ export type SetupStepId =
|
||||
export type SetupCategory =
|
||||
| 'Infrastructure'
|
||||
| 'Security'
|
||||
| 'Configuration'
|
||||
| 'Integration'
|
||||
| 'Observability'
|
||||
| 'Data'
|
||||
| 'Orchestration';
|
||||
| 'Release Control Plane'
|
||||
| 'Observability';
|
||||
|
||||
/** Status of an individual setup step */
|
||||
export type SetupStepStatus =
|
||||
@@ -1064,7 +1062,7 @@ export const DEFAULT_SETUP_STEPS: SetupStep[] = [
|
||||
id: 'vault',
|
||||
name: 'Secrets Vault',
|
||||
description: 'Configure a secrets vault for secure credential storage (HashiCorp Vault, Azure Key Vault, AWS Secrets Manager, or GCP Secret Manager).',
|
||||
category: 'Security',
|
||||
category: 'Integration',
|
||||
order: 60,
|
||||
isRequired: false,
|
||||
isSkippable: true,
|
||||
@@ -1110,7 +1108,7 @@ export const DEFAULT_SETUP_STEPS: SetupStep[] = [
|
||||
id: 'sources',
|
||||
name: 'Advisory Data Sources',
|
||||
description: 'Configure CVE/VEX advisory feeds (NVD, GHSA, OSV, distribution-specific feeds) for vulnerability data.',
|
||||
category: 'Data',
|
||||
category: 'Release Control Plane',
|
||||
order: 90,
|
||||
isRequired: false,
|
||||
isSkippable: true,
|
||||
@@ -1121,28 +1119,13 @@ export const DEFAULT_SETUP_STEPS: SetupStep[] = [
|
||||
configureLaterCliCommand: 'stella config set sources.*',
|
||||
skipWarning: 'CVE/VEX advisory feeds will require manual updates.',
|
||||
},
|
||||
// Phase 5: Observability (Optional)
|
||||
{
|
||||
id: 'telemetry',
|
||||
name: 'OpenTelemetry',
|
||||
description: 'Configure OpenTelemetry for distributed tracing, metrics, and logging.',
|
||||
category: 'Observability',
|
||||
order: 100,
|
||||
isRequired: false,
|
||||
isSkippable: true,
|
||||
dependencies: [],
|
||||
validationChecks: ['check.telemetry.otlp.connectivity'],
|
||||
status: 'pending',
|
||||
configureLaterUiPath: 'Settings → System → Telemetry',
|
||||
configureLaterCliCommand: 'stella config set telemetry.*',
|
||||
skipWarning: 'System observability will be limited. Tracing and metrics unavailable.',
|
||||
},
|
||||
// Phase 5: Notifications (Optional)
|
||||
{
|
||||
id: 'notify',
|
||||
name: 'Notifications',
|
||||
description: 'Configure notification channels (Email, Slack, Teams, Webhook) for alerts and events.',
|
||||
category: 'Integration',
|
||||
order: 110,
|
||||
order: 100,
|
||||
isRequired: false,
|
||||
isSkippable: true,
|
||||
dependencies: [],
|
||||
@@ -1172,7 +1155,7 @@ export const DEFAULT_SETUP_STEPS: SetupStep[] = [
|
||||
id: 'settingsstore',
|
||||
name: 'Settings Store',
|
||||
description: 'Configure an external settings store for application configuration and feature flags (Consul, etcd, Azure App Configuration, or AWS Parameter Store).',
|
||||
category: 'Configuration',
|
||||
category: 'Release Control Plane',
|
||||
order: 130,
|
||||
isRequired: false,
|
||||
isSkippable: true,
|
||||
@@ -1187,7 +1170,7 @@ export const DEFAULT_SETUP_STEPS: SetupStep[] = [
|
||||
id: 'environments',
|
||||
name: 'Deployment Environments',
|
||||
description: 'Define deployment environments for release orchestration (e.g., dev, staging, production).',
|
||||
category: 'Orchestration',
|
||||
category: 'Release Control Plane',
|
||||
order: 140,
|
||||
isRequired: false,
|
||||
isSkippable: true,
|
||||
@@ -1201,7 +1184,7 @@ export const DEFAULT_SETUP_STEPS: SetupStep[] = [
|
||||
id: 'agents',
|
||||
name: 'Deployment Agents',
|
||||
description: 'Register deployment agents that will execute releases to your environments.',
|
||||
category: 'Orchestration',
|
||||
category: 'Release Control Plane',
|
||||
order: 150,
|
||||
isRequired: false,
|
||||
isSkippable: true,
|
||||
@@ -1212,4 +1195,20 @@ export const DEFAULT_SETUP_STEPS: SetupStep[] = [
|
||||
configureLaterCliCommand: 'stella agent register',
|
||||
skipWarning: 'Release orchestration will not be available without registered agents.',
|
||||
},
|
||||
// Phase 9: Observability (Optional — last step)
|
||||
{
|
||||
id: 'telemetry',
|
||||
name: 'OpenTelemetry',
|
||||
description: 'Configure OpenTelemetry for distributed tracing, metrics, and logging.',
|
||||
category: 'Observability',
|
||||
order: 160,
|
||||
isRequired: false,
|
||||
isSkippable: true,
|
||||
dependencies: [],
|
||||
validationChecks: ['check.telemetry.otlp.connectivity'],
|
||||
status: 'pending',
|
||||
configureLaterUiPath: 'Settings → System → Telemetry',
|
||||
configureLaterCliCommand: 'stella config set telemetry.*',
|
||||
skipWarning: 'System observability will be limited. Tracing and metrics unavailable.',
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user