Search/AdvisoryAI and DAL conversion to EF finishes up. Preparation for microservices consolidation.

This commit is contained in:
master
2026-02-25 18:19:22 +02:00
parent 4db038123b
commit 63c70a6d37
447 changed files with 52257 additions and 2636 deletions

View File

@@ -427,7 +427,8 @@ export class UnifiedSearchClient {
}
return value
.replace(/<\/?mark>/gi, '')
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, ' ')
.replace(/<[^>]+>/g, ' ')
.replace(/\s+/g, ' ')
.trim();
}
@@ -531,6 +532,9 @@ export class UnifiedSearchClient {
avgResultCount: 0,
feedbackScore: 0,
period,
lowQualityResults: [],
topQueries: [],
trend: [],
}),
),
);
@@ -540,7 +544,7 @@ export class UnifiedSearchClient {
// --- Search analytics types (Sprint 106 / G6) ---
export interface SearchAnalyticsEventDto {
eventType: 'query' | 'click' | 'zero_result';
eventType: 'query' | 'click' | 'zero_result' | 'synthesis';
query: string;
entityKey?: string;
domain?: string;

View File

@@ -171,9 +171,34 @@ export interface SearchQualityMetrics {
avgResultCount: number;
feedbackScore: number;
period: string;
lowQualityResults: SearchLowQualityResult[];
topQueries: SearchTopQuery[];
trend: SearchQualityTrendPoint[];
}
export interface SearchQualityAlertUpdateRequest {
status: 'acknowledged' | 'resolved';
resolution?: string;
}
export interface SearchLowQualityResult {
entityKey: string;
domain: string;
negativeFeedbackCount: number;
totalFeedback: number;
negativeRate: number;
}
export interface SearchTopQuery {
query: string;
totalSearches: number;
avgResultCount: number;
feedbackScore: number;
}
export interface SearchQualityTrendPoint {
day: string;
totalSearches: number;
zeroResultRate: number;
feedbackScore: number;
}

View File

@@ -1,13 +1,80 @@
import { Injectable, inject } from '@angular/core';
import { Router } from '@angular/router';
import { Injectable, inject, signal } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { filter } from 'rxjs/operators';
import type { UnifiedSearchDomain, UnifiedSearchFilter } from '../api/unified-search.models';
export interface ContextSuggestion {
key: string;
fallback: string;
}
@Injectable({ providedIn: 'root' })
export class AmbientContextService {
private readonly router = inject(Router);
private readonly routeUrl = signal(this.router.url);
private readonly searchSuggestionSets = {
findings: [
{ key: 'ui.search.suggestion.findings.critical', fallback: 'critical findings' },
{ key: 'ui.search.suggestion.findings.reachable', fallback: 'reachable vulnerabilities' },
{ key: 'ui.search.suggestion.findings.unresolved', fallback: 'unresolved CVEs' },
],
policy: [
{ key: 'ui.search.suggestion.policy.failing_gates', fallback: 'failing policy gates' },
{ key: 'ui.search.suggestion.policy.production_deny', fallback: 'production deny rules' },
{ key: 'ui.search.suggestion.policy.exceptions', fallback: 'policy exceptions' },
],
doctor: [
{ key: 'ui.search.suggestion.doctor.database', fallback: 'database connectivity' },
{ key: 'ui.search.suggestion.doctor.disk', fallback: 'disk space' },
{ key: 'ui.search.suggestion.doctor.oidc', fallback: 'OIDC readiness' },
],
timeline: [
{ key: 'ui.search.suggestion.timeline.failed_deployments', fallback: 'failed deployments' },
{ key: 'ui.search.suggestion.timeline.recent_promotions', fallback: 'recent promotions' },
{ key: 'ui.search.suggestion.timeline.release_history', fallback: 'release history' },
],
releases: [
{ key: 'ui.search.suggestion.releases.pending_approvals', fallback: 'pending approvals' },
{ key: 'ui.search.suggestion.releases.blocked_releases', fallback: 'blocked releases' },
{ key: 'ui.search.suggestion.releases.environment_status', fallback: 'environment status' },
],
default: [
{ key: 'ui.search.suggestion.default.deploy', fallback: 'How do I deploy?' },
{ key: 'ui.search.suggestion.default.vex', fallback: 'What is a VEX statement?' },
{ key: 'ui.search.suggestion.default.critical', fallback: 'Show critical findings' },
],
} as const satisfies Record<string, readonly ContextSuggestion[]>;
private readonly chatSuggestionSets = {
vulnerability: [
{ key: 'ui.chat.suggestion.vulnerability.exploitable', fallback: 'Is this exploitable in my environment?' },
{ key: 'ui.chat.suggestion.vulnerability.remediation', fallback: 'What is the remediation?' },
{ key: 'ui.chat.suggestion.vulnerability.evidence_chain', fallback: 'Show me the evidence chain' },
{ key: 'ui.chat.suggestion.vulnerability.draft_vex', fallback: 'Draft a VEX statement' },
],
policy: [
{ key: 'ui.chat.suggestion.policy.explain_rule', fallback: 'Explain this policy rule' },
{ key: 'ui.chat.suggestion.policy.override_gate', fallback: 'What would happen if I override this gate?' },
{ key: 'ui.chat.suggestion.policy.recent_violations', fallback: 'Show me recent policy violations' },
{ key: 'ui.chat.suggestion.policy.add_exception', fallback: 'How do I add an exception?' },
],
default: [
{ key: 'ui.chat.suggestion.default.what_can_do', fallback: 'What can Stella Ops do?' },
{ key: 'ui.chat.suggestion.default.first_scan', fallback: 'How do I set up my first scan?' },
{ key: 'ui.chat.suggestion.default.promotion_workflow', fallback: 'Explain the release promotion workflow' },
{ key: 'ui.chat.suggestion.default.health_checks', fallback: 'What health checks should I run first?' },
],
} as const satisfies Record<string, readonly ContextSuggestion[]>;
constructor() {
this.router.events
.pipe(filter((event): event is NavigationEnd => event instanceof NavigationEnd))
.subscribe((event) => this.routeUrl.set(event.urlAfterRedirects));
}
currentDomain(): UnifiedSearchDomain | null {
const url = this.router.url;
const url = this.routeUrl();
if (url.startsWith('/security/triage') || url.startsWith('/security/findings')) {
return 'findings';
@@ -40,6 +107,46 @@ export class AmbientContextService {
return null;
}
getSearchSuggestions(): readonly ContextSuggestion[] {
const url = this.routeUrl();
if (url.startsWith('/security/triage') || url.startsWith('/security/findings')) {
return this.searchSuggestionSets.findings;
}
if (url.startsWith('/ops/policy')) {
return this.searchSuggestionSets.policy;
}
if (url.startsWith('/ops/operations/doctor') || url.startsWith('/ops/operations/system-health')) {
return this.searchSuggestionSets.doctor;
}
if (url.startsWith('/ops/timeline') || url.startsWith('/audit')) {
return this.searchSuggestionSets.timeline;
}
if (url.startsWith('/releases') || url.startsWith('/mission-control')) {
return this.searchSuggestionSets.releases;
}
return this.searchSuggestionSets.default;
}
getChatSuggestions(): readonly ContextSuggestion[] {
const url = this.routeUrl();
if (url.match(/\/security\/(findings|triage)\/[^/]+/)) {
return this.chatSuggestionSets.vulnerability;
}
if (url.startsWith('/ops/policy')) {
return this.chatSuggestionSets.policy;
}
return this.chatSuggestionSets.default;
}
buildContextFilter(): UnifiedSearchFilter {
const domain = this.currentDomain();
if (!domain) {

View File

@@ -20,10 +20,11 @@ import {
} from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import { Subject, takeUntil } from 'rxjs';
import { ChatService } from './chat.service';
import { ChatMessageComponent } from './chat-message.component';
import { AmbientContextService } from '../../../core/services/ambient-context.service';
import { I18nService } from '../../../core/i18n';
import {
Conversation,
ConversationContext,
@@ -552,7 +553,8 @@ export class ChatComponent implements OnInit, OnDestroy {
@ViewChild('inputField') inputField!: ElementRef<HTMLTextAreaElement>;
private readonly chatService = inject(ChatService);
private readonly router = inject(Router);
private readonly ambientContext = inject(AmbientContextService);
private readonly i18n = inject(I18nService);
private readonly destroy$ = new Subject<void>();
private pendingInitialMessage: string | null = null;
@@ -571,51 +573,16 @@ export class ChatComponent implements OnInit, OnDestroy {
});
readonly inputPlaceholder = computed(() => {
if (this.isStreaming()) return 'Waiting for response...';
return 'Ask AdvisoryAI about this finding...';
if (this.isStreaming()) {
return this.i18n.tryT('ui.chat.input.waiting') ?? 'Waiting for response...';
}
return this.i18n.tryT('ui.chat.input.placeholder') ?? 'Ask AdvisoryAI about this finding...';
});
readonly suggestions = computed(() => {
const url = this.router.url;
// Vulnerability detail pages: keep original context-specific suggestions
if (url.match(/\/security\/(findings|triage)\/[^/]+/)) {
return [
'Is this exploitable?',
'What is the remediation?',
'Show me the evidence',
'Create a VEX statement',
];
}
// Policy pages
if (url.startsWith('/ops/policy')) {
return [
'Explain the release promotion workflow',
'What policy gates are failing?',
'How do I create a policy exception?',
'What health checks should I run first?',
];
}
// Doctor / health pages
if (url.startsWith('/ops/operations/doctor')) {
return [
'What health checks should I run first?',
'How do I troubleshoot database connectivity?',
'Explain the system readiness checks',
'What can Stella Ops do?',
];
}
// Default: general onboarding suggestions
return [
'What can Stella Ops do?',
'How do I set up my first scan?',
'Explain the release promotion workflow',
'What health checks should I run first?',
];
});
readonly suggestions = computed(() =>
this.ambientContext
.getChatSuggestions()
.map((suggestion) => this.i18n.tryT(suggestion.key) ?? suggestion.fallback));
constructor() {
// Auto-scroll on new content

View File

@@ -13,6 +13,9 @@ import { takeUntil } from 'rxjs/operators';
import { UnifiedSearchClient } from '../../../core/api/unified-search.client';
import type {
SearchQualityAlert,
SearchQualityTrendPoint,
SearchLowQualityResult,
SearchTopQuery,
SearchQualityMetrics,
} from '../../../core/api/unified-search.models';
@@ -73,7 +76,7 @@ import type {
<table class="sqd__table">
<thead>
<tr>
<th>Query</th>
<th>Query Hash</th>
<th>Type</th>
<th>Occurrences</th>
<th>First Seen</th>
@@ -134,6 +137,97 @@ import type {
</div>
}
</div>
<div class="sqd__section">
<h2 class="sqd__section-title">Low-Quality Results</h2>
@if (lowQualityResults().length === 0) {
<div class="sqd__empty">No low-quality result signals yet.</div>
} @else {
<div class="sqd__table-wrapper">
<table class="sqd__table">
<thead>
<tr>
<th>Entity</th>
<th>Domain</th>
<th>Negative</th>
<th>Total</th>
<th>Negative Rate</th>
</tr>
</thead>
<tbody>
@for (row of lowQualityResults(); track row.entityKey + row.domain) {
<tr>
<td class="sqd__query-cell">{{ row.entityKey }}</td>
<td>{{ row.domain }}</td>
<td class="sqd__count-cell">{{ row.negativeFeedbackCount }}</td>
<td class="sqd__count-cell">{{ row.totalFeedback }}</td>
<td class="sqd__count-cell">{{ row.negativeRate }}%</td>
</tr>
}
</tbody>
</table>
</div>
}
</div>
<div class="sqd__section">
<h2 class="sqd__section-title">Top Query Hashes</h2>
@if (topQueries().length === 0) {
<div class="sqd__empty">No query-hash history available for this period.</div>
} @else {
<div class="sqd__table-wrapper">
<table class="sqd__table">
<thead>
<tr>
<th>Query Hash</th>
<th>Searches</th>
<th>Avg Results</th>
<th>Feedback Score</th>
</tr>
</thead>
<tbody>
@for (row of topQueries(); track row.query) {
<tr>
<td class="sqd__query-cell">{{ row.query }}</td>
<td class="sqd__count-cell">{{ row.totalSearches }}</td>
<td class="sqd__count-cell">{{ row.avgResultCount }}</td>
<td class="sqd__count-cell">{{ row.feedbackScore }}%</td>
</tr>
}
</tbody>
</table>
</div>
}
</div>
<div class="sqd__section">
<h2 class="sqd__section-title">30-Day Trend</h2>
@if (trend().length === 0) {
<div class="sqd__empty">No trend data available yet.</div>
} @else {
<div class="sqd__trend-card">
<svg
class="sqd__trend-svg"
viewBox="0 0 1000 220"
role="img"
aria-label="Search quality trend chart"
>
<polyline class="sqd__trend-line sqd__trend-line--searches" [attr.points]="trendSearchPath()" />
<polyline class="sqd__trend-line sqd__trend-line--zero" [attr.points]="trendZeroRatePath()" />
<polyline class="sqd__trend-line sqd__trend-line--feedback" [attr.points]="trendFeedbackPath()" />
</svg>
<div class="sqd__trend-legend">
<span class="sqd__legend-item"><span class="sqd__legend-swatch sqd__legend-swatch--searches"></span>Searches</span>
<span class="sqd__legend-item"><span class="sqd__legend-swatch sqd__legend-swatch--zero"></span>Zero-Result %</span>
<span class="sqd__legend-item"><span class="sqd__legend-swatch sqd__legend-swatch--feedback"></span>Feedback %</span>
</div>
<div class="sqd__trend-axis">
<span>{{ formatDay(trend()[0]?.day) }}</span>
<span>{{ formatDay(trend()[trend().length - 1]?.day) }}</span>
</div>
</div>
}
</div>
</div>
`,
styles: [`
@@ -369,6 +463,81 @@ import type {
color: #6b7280;
font-style: italic;
}
.sqd__trend-card {
border: 1px solid #e5e7eb;
border-radius: 8px;
background: #ffffff;
padding: 0.75rem;
}
.sqd__trend-svg {
width: 100%;
height: 220px;
border-radius: 6px;
background: linear-gradient(180deg, #f8fafc 0%, #ffffff 100%);
}
.sqd__trend-line {
fill: none;
stroke-width: 3;
stroke-linejoin: round;
stroke-linecap: round;
}
.sqd__trend-line--searches {
stroke: #1d4ed8;
}
.sqd__trend-line--zero {
stroke: #dc2626;
}
.sqd__trend-line--feedback {
stroke: #16a34a;
}
.sqd__trend-legend {
margin-top: 0.5rem;
display: flex;
gap: 1rem;
flex-wrap: wrap;
font-size: 0.75rem;
color: #374151;
}
.sqd__legend-item {
display: inline-flex;
align-items: center;
gap: 0.35rem;
}
.sqd__legend-swatch {
width: 0.9rem;
height: 0.3rem;
border-radius: 999px;
display: inline-block;
}
.sqd__legend-swatch--searches {
background: #1d4ed8;
}
.sqd__legend-swatch--zero {
background: #dc2626;
}
.sqd__legend-swatch--feedback {
background: #16a34a;
}
.sqd__trend-axis {
display: flex;
justify-content: space-between;
margin-top: 0.4rem;
font-size: 0.72rem;
color: #6b7280;
}
`],
changeDetection: ChangeDetectionStrategy.OnPush,
})
@@ -386,6 +555,25 @@ export class SearchQualityDashboardComponent implements OnInit, OnDestroy {
readonly metrics = signal<SearchQualityMetrics | null>(null);
readonly alerts = signal<SearchQualityAlert[]>([]);
readonly isLoadingAlerts = signal(true);
readonly lowQualityResults = computed<SearchLowQualityResult[]>(
() => this.metrics()?.lowQualityResults ?? [],
);
readonly topQueries = computed<SearchTopQuery[]>(
() => this.metrics()?.topQueries ?? [],
);
readonly trend = computed<SearchQualityTrendPoint[]>(
() => this.metrics()?.trend ?? [],
);
readonly maxTrendSearchVolume = computed(() => {
const values = this.trend().map((point) => point.totalSearches);
return Math.max(1, ...values);
});
readonly trendSearchPath = computed(() =>
this.buildTrendPath(this.trend(), (point) => point.totalSearches, this.maxTrendSearchVolume()));
readonly trendZeroRatePath = computed(() =>
this.buildTrendPath(this.trend(), (point) => point.zeroResultRate, 100));
readonly trendFeedbackPath = computed(() =>
this.buildTrendPath(this.trend(), (point) => point.feedbackScore, 100));
ngOnInit(): void {
this.loadMetrics('7d');
@@ -447,4 +635,42 @@ export class SearchQualityDashboardComponent implements OnInit, OnDestroy {
return iso;
}
}
formatDay(day: string | undefined): string {
if (!day) {
return '';
}
try {
const d = new Date(day);
return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
}
catch {
return day;
}
}
private buildTrendPath(
points: SearchQualityTrendPoint[],
selector: (point: SearchQualityTrendPoint) => number,
maxValue: number,
): string {
if (points.length === 0) {
return '';
}
const width = 1000;
const height = 220;
const safeMax = Math.max(1, maxValue);
const stepX = points.length <= 1 ? 0 : width / (points.length - 1);
return points
.map((point, index) => {
const value = Math.max(0, Math.min(safeMax, selector(point)));
const x = index * stepX;
const y = height - (value / safeMax) * height;
return `${x.toFixed(2)},${y.toFixed(2)}`;
})
.join(' ');
}
}

View File

@@ -34,6 +34,7 @@ import { EntityCardComponent } from '../../shared/components/entity-card/entity-
import { SynthesisPanelComponent } from '../../shared/components/synthesis-panel/synthesis-panel.component';
import { AmbientContextService } from '../../core/services/ambient-context.service';
import { SearchChatContextService } from '../../core/services/search-chat-context.service';
import { I18nService } from '../../core/i18n';
import { normalizeSearchActionRoute } from './search-route-matrix';
type SearchDomainFilter = 'all' | UnifiedSearchDomain;
@@ -60,7 +61,7 @@ type SearchDomainFilter = 'all' | UnifiedSearchDomain;
(focus)="onFocus()"
(blur)="onBlur()"
(keydown)="onKeydown($event)"
aria-label="Global search"
[attr.aria-label]="t('ui.search.input_aria_label', 'Global search')"
aria-autocomplete="list"
/>
@@ -69,20 +70,20 @@ type SearchDomainFilter = 'all' | UnifiedSearchDomain;
@if (showResults()) {
<div class="search__results" id="search-results">
@if (showDegradedModeBanner()) {
<div class="search__degraded-banner" role="status" aria-live="polite">
<span class="search__degraded-title">Fallback mode:</span>
{{ degradedModeMessage() }}
</div>
}
@if (showDegradedModeBanner()) {
<div class="search__degraded-banner" role="status" aria-live="polite">
<span class="search__degraded-title">{{ degradedModeLabel() }}</span>
{{ degradedModeMessage() }}
</div>
}
@if (isLoading()) {
<div class="search__loading">Searching...</div>
<div class="search__loading">{{ t('ui.search.loading', 'Searching...') }}</div>
} @else if (query().trim().length >= 1 && cards().length === 0) {
<div class="search__empty">No results found</div>
<div class="search__empty">{{ t('ui.search.no_results', 'No results found') }}</div>
@if (searchResponse()?.suggestions?.length) {
<div class="did-you-mean">
<span class="did-you-mean__label">Did you mean:</span>
<span class="did-you-mean__label">{{ t('ui.search.did_you_mean_label', 'Did you mean:') }}</span>
@for (suggestion of searchResponse()!.suggestions!; track suggestion.text) {
<button
type="button"
@@ -96,7 +97,7 @@ type SearchDomainFilter = 'all' | UnifiedSearchDomain;
}
@if (refinements().length > 0) {
<div class="try-also-bar">
<span class="try-also-bar__label">Try also:</span>
<span class="try-also-bar__label">{{ t('ui.search.try_also_label', 'Try also:') }}</span>
@for (r of refinements(); track r.text) {
<button
type="button"
@@ -123,7 +124,7 @@ type SearchDomainFilter = 'all' | UnifiedSearchDomain;
@if (searchResponse()?.suggestions?.length) {
<div class="did-you-mean">
<span class="did-you-mean__label">Did you mean:</span>
<span class="did-you-mean__label">{{ t('ui.search.did_you_mean_label', 'Did you mean:') }}</span>
@for (suggestion of searchResponse()!.suggestions!; track suggestion.text) {
<button
type="button"
@@ -137,7 +138,7 @@ type SearchDomainFilter = 'all' | UnifiedSearchDomain;
}
@if (refinements().length > 0) {
<div class="try-also-bar">
<span class="try-also-bar__label">Try also:</span>
<span class="try-also-bar__label">{{ t('ui.search.try_also_label', 'Try also:') }}</span>
@for (r of refinements(); track r.text) {
<button
type="button"
@@ -173,8 +174,8 @@ type SearchDomainFilter = 'all' | UnifiedSearchDomain;
@if (recentSearches().length > 0) {
<div class="search__group">
<div class="search__group-header">
<div class="search__group-label">Recent</div>
<button type="button" class="search__clear-history" (click)="clearSearchHistory()">Clear</button>
<div class="search__group-label">{{ t('ui.search.recent_label', 'Recent') }}</div>
<button type="button" class="search__clear-history" (click)="clearSearchHistory()">{{ t('ui.search.clear_history', 'Clear') }}</button>
</div>
@for (recent of recentSearches(); track recent; let i = $index) {
<button
@@ -195,7 +196,7 @@ type SearchDomainFilter = 'all' | UnifiedSearchDomain;
}
<div class="search__suggestions">
<div class="search__group-label">Suggested</div>
<div class="search__group-label">{{ t('ui.search.suggested_label', 'Suggested') }}</div>
<div class="search__suggestion-chips">
@for (suggestion of contextualSuggestions(); track suggestion) {
<button
@@ -208,9 +209,9 @@ type SearchDomainFilter = 'all' | UnifiedSearchDomain;
</div>
<div class="search__domain-guide">
<div class="search__group-label">Search across your release control plane</div>
<div class="search__group-label">{{ t('ui.search.empty_state_header', 'Search across your release control plane') }}</div>
<div class="search__domain-grid">
@for (domain of domainGuide; track domain.title) {
@for (domain of domainGuide(); track domain.key) {
<div class="search__domain-card">
<div class="search__domain-title">
<span class="search__domain-icon" aria-hidden="true">{{ domain.icon }}</span>
@@ -233,13 +234,21 @@ type SearchDomainFilter = 'all' | UnifiedSearchDomain;
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>
</svg>
Getting Started
{{ t('ui.search.quick_action.getting_started', 'Getting Started') }}
</button>
<button type="button" class="search__quick-link" (click)="navigateQuickAction('/ops/operations/doctor')">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>
</svg>
Run Health Check
{{ t('ui.search.quick_action.run_health_check', 'Run Health Check') }}
</button>
<button type="button" class="search__quick-link" (click)="navigateQuickAction('/security/triage')">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 3h18v4H3z"/>
<path d="M5 11h14"/>
<path d="M5 16h10"/>
</svg>
{{ t('ui.search.quick_action.view_recent_scans', 'View Recent Scans') }}
</button>
</div>
}
@@ -484,7 +493,7 @@ type SearchDomainFilter = 'all' | UnifiedSearchDomain;
padding: 0.25rem 0.75rem;
}
@media (max-width: 480px) {
@media (max-width: 767px) {
.search__domain-grid {
grid-template-columns: 1fr;
}
@@ -646,12 +655,14 @@ export class GlobalSearchComponent implements OnInit, OnDestroy {
private readonly searchClient = inject(UnifiedSearchClient);
private readonly ambientContext = inject(AmbientContextService);
private readonly searchChatContext = inject(SearchChatContextService);
private readonly i18n = inject(I18nService);
private readonly destroy$ = new Subject<void>();
private readonly searchTerms$ = new Subject<string>();
private readonly recentSearchStorageKey = 'stella-recent-searches';
private wasDegradedMode = false;
private escapeCount = 0;
private placeholderRotationHandle: ReturnType<typeof setInterval> | null = null;
private blurHideHandle: ReturnType<typeof setTimeout> | null = null;
@ViewChild('searchInput') searchInputRef!: ElementRef<HTMLInputElement>;
@@ -677,6 +688,8 @@ export class GlobalSearchComponent implements OnInit, OnDestroy {
const mode = this.diagnosticsMode();
return mode === 'legacy-fallback' || mode === 'fallback-empty';
});
readonly degradedModeLabel = computed(() =>
this.i18n.tryT('ui.search.degraded.label') ?? 'Fallback mode:');
readonly showDegradedModeBanner = computed(() =>
!this.isLoading() &&
this.query().trim().length >= 1 &&
@@ -684,54 +697,121 @@ export class GlobalSearchComponent implements OnInit, OnDestroy {
);
readonly degradedModeMessage = computed(() => {
if (this.diagnosticsMode() === 'fallback-empty') {
return 'Unified search is unavailable and legacy fallback returned no results. Try a broader query or retry.';
return this.i18n.tryT('ui.search.degraded.empty') ??
'Unified search is unavailable and legacy fallback returned no results. Try a broader query or retry.';
}
return 'Showing legacy fallback results. Coverage and ranking may differ until unified search recovers.';
return this.i18n.tryT('ui.search.degraded.results') ??
'Showing legacy fallback results. Coverage and ranking may differ until unified search recovers.';
});
readonly domainGuide: ReadonlyArray<{
icon: string;
title: string;
description: string;
example: string;
}> = [
{ icon: 'S', title: 'Security Findings', description: 'CVEs, vulnerabilities, and exposure data', example: 'CVE-2024-21626' },
{ icon: 'V', title: 'VEX Statements', description: 'Vulnerability exploitability assessments', example: 'not_affected' },
{ icon: 'P', title: 'Policy Rules', description: 'Release gate rules and enforcement', example: 'DENY-CRITICAL-PROD' },
{ icon: 'D', title: 'Documentation', description: 'Guides, architecture, and runbooks', example: 'how to deploy' },
{ icon: 'A', title: 'API Reference', description: 'OpenAPI endpoints and contracts', example: 'POST /api/v1/scanner/scans' },
{ icon: 'H', title: 'Health Checks', description: 'System diagnostics and remediation', example: 'database connectivity' },
{ icon: 'R', title: 'Release Workflows', description: 'Promotion status, rollout history, and gate decisions', example: 'failed promotion' },
{ icon: 'C', title: 'Platform Catalog', description: 'Components, integrations, and environment inventory', example: 'registry integration' },
];
private readonly domainGuideCatalog = [
{
key: 'findings',
icon: 'S',
titleKey: 'ui.search.domain.findings.title',
titleFallback: 'Security Findings',
descriptionKey: 'ui.search.domain.findings.description',
descriptionFallback: 'CVEs, vulnerabilities, and exposure data across your images.',
exampleKey: 'ui.search.domain.findings.example',
exampleFallback: 'CVE-2024-21626',
},
{
key: 'vex',
icon: 'V',
titleKey: 'ui.search.domain.vex.title',
titleFallback: 'VEX Statements',
descriptionKey: 'ui.search.domain.vex.description',
descriptionFallback: 'Exploitability statuses and vendor assertions for vulnerabilities.',
exampleKey: 'ui.search.domain.vex.example',
exampleFallback: 'not_affected',
},
{
key: 'policy',
icon: 'P',
titleKey: 'ui.search.domain.policy.title',
titleFallback: 'Policy Rules',
descriptionKey: 'ui.search.domain.policy.description',
descriptionFallback: 'Release gate rules, exceptions, and enforcement outcomes.',
exampleKey: 'ui.search.domain.policy.example',
exampleFallback: 'DENY-CRITICAL-PROD',
},
{
key: 'docs',
icon: 'D',
titleKey: 'ui.search.domain.docs.title',
titleFallback: 'Documentation',
descriptionKey: 'ui.search.domain.docs.description',
descriptionFallback: 'Guides, architecture references, and operator runbooks.',
exampleKey: 'ui.search.domain.docs.example',
exampleFallback: 'how to deploy',
},
{
key: 'api',
icon: 'A',
titleKey: 'ui.search.domain.api.title',
titleFallback: 'API Reference',
descriptionKey: 'ui.search.domain.api.description',
descriptionFallback: 'OpenAPI endpoints, request contracts, and service operations.',
exampleKey: 'ui.search.domain.api.example',
exampleFallback: 'POST /api/v1/scanner/scans',
},
{
key: 'health',
icon: 'H',
titleKey: 'ui.search.domain.health.title',
titleFallback: 'Health Checks',
descriptionKey: 'ui.search.domain.health.description',
descriptionFallback: 'Doctor checks, readiness diagnostics, and remediation guidance.',
exampleKey: 'ui.search.domain.health.example',
exampleFallback: 'database connectivity',
},
{
key: 'operations',
icon: 'O',
titleKey: 'ui.search.domain.operations.title',
titleFallback: 'Operations',
descriptionKey: 'ui.search.domain.operations.description',
descriptionFallback: 'Jobs, workflows, and operational controls across environments.',
exampleKey: 'ui.search.domain.operations.example',
exampleFallback: 'blocked releases',
},
{
key: 'timeline',
icon: 'T',
titleKey: 'ui.search.domain.timeline.title',
titleFallback: 'Timeline',
descriptionKey: 'ui.search.domain.timeline.description',
descriptionFallback: 'Promotion history, incidents, and causal event traces.',
exampleKey: 'ui.search.domain.timeline.example',
exampleFallback: 'release history',
},
] as const;
readonly contextualSuggestions = computed<string[]>(() => {
const url = this.router.url;
readonly domainGuide = computed(() =>
this.domainGuideCatalog.map((domain) => ({
key: domain.key,
icon: domain.icon,
title: this.t(domain.titleKey, domain.titleFallback),
description: this.t(domain.descriptionKey, domain.descriptionFallback),
example: this.t(domain.exampleKey, domain.exampleFallback),
})));
if (url.startsWith('/security/triage') || url.startsWith('/security/findings')) {
return ['critical findings', 'reachable vulnerabilities', 'unresolved CVEs'];
}
if (url.startsWith('/ops/policy')) {
return ['failing policy gates', 'production deny rules', 'policy exceptions'];
}
if (url.startsWith('/ops/operations/doctor')) {
return ['database connectivity', 'disk space', 'OIDC readiness'];
}
return ['How do I deploy?', 'What is a VEX statement?', 'Show critical findings'];
});
readonly contextualSuggestions = computed<string[]>(() =>
this.ambientContext
.getSearchSuggestions()
.map((suggestion) => this.i18n.tryT(suggestion.key) ?? suggestion.fallback));
readonly inputPlaceholder = computed(() => {
const suggestions = this.contextualSuggestions();
if (suggestions.length === 0) {
return 'Search everything...';
return this.t('ui.search.placeholder.default', 'Search everything...');
}
const index = this.placeholderIndex() % suggestions.length;
return `Try: ${suggestions[index]}`;
return this.t('ui.search.placeholder.try', 'Try: {suggestion}', {
suggestion: suggestions[index],
});
});
readonly cards = computed(() => this.searchResponse()?.cards ?? []);
@@ -763,8 +843,10 @@ export class GlobalSearchComponent implements OnInit, OnDestroy {
ngOnInit(): void {
this.placeholderRotationHandle = setInterval(() => {
this.placeholderIndex.update((current) => current + 1);
}, 4500);
if (!this.isFocused()) {
this.placeholderIndex.update((current) => current + 1);
}
}, 3000);
this.searchChatContext.chatToSearchRequested$
.pipe(takeUntil(this.destroy$))
@@ -844,6 +926,10 @@ export class GlobalSearchComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
if (this.blurHideHandle) {
clearTimeout(this.blurHideHandle);
this.blurHideHandle = null;
}
if (this.placeholderRotationHandle) {
clearInterval(this.placeholderRotationHandle);
this.placeholderRotationHandle = null;
@@ -851,6 +937,10 @@ export class GlobalSearchComponent implements OnInit, OnDestroy {
}
onFocus(): void {
if (this.blurHideHandle) {
clearTimeout(this.blurHideHandle);
this.blurHideHandle = null;
}
this.isFocused.set(true);
this.escapeCount = 0;
this.consumeChatToSearchContext();
@@ -859,7 +949,19 @@ export class GlobalSearchComponent implements OnInit, OnDestroy {
}
onBlur(): void {
setTimeout(() => {
if (this.blurHideHandle) {
clearTimeout(this.blurHideHandle);
}
this.blurHideHandle = setTimeout(() => {
this.blurHideHandle = null;
const input = this.searchInputRef?.nativeElement;
const activeElement = typeof document !== 'undefined'
? document.activeElement
: null;
if (input && activeElement === input) {
return;
}
this.isFocused.set(false);
this.escapeCount = 0;
}, 200);
@@ -1041,9 +1143,14 @@ export class GlobalSearchComponent implements OnInit, OnDestroy {
this.selectedIndex.set(0);
}
t(key: string, fallback: string, params?: Record<string, string | number>): string {
return this.i18n.tryT(key, params) ?? fallback;
}
getDomainFilterLabel(filter: SearchDomainFilter): string {
if (filter === 'all') {
return `All (${this.cards().length})`;
const allLabel = this.t('ui.search.filter.all', 'All');
return `${allLabel} (${this.cards().length})`;
}
const count = this.cards().filter((c) => c.domain === filter).length;
@@ -1089,6 +1196,7 @@ export class GlobalSearchComponent implements OnInit, OnDestroy {
const primaryAction = selected.actions.find((a) => a.isPrimary) ?? selected.actions[0];
if (primaryAction) {
this.saveRecentSearch(this.query());
this.emitClickAnalytics(selected);
this.executeAction(primaryAction);
this.closeResults();
}
@@ -1179,8 +1287,10 @@ export class GlobalSearchComponent implements OnInit, OnDestroy {
}
const events: Array<{
eventType: 'query' | 'zero_result';
eventType: 'query' | 'zero_result' | 'synthesis';
query: string;
entityKey?: string;
domain?: string;
resultCount: number;
durationMs?: number;
}> = [{
@@ -1199,6 +1309,17 @@ export class GlobalSearchComponent implements OnInit, OnDestroy {
});
}
if (response.synthesis) {
events.push({
eventType: 'synthesis' as const,
query: normalized,
entityKey: '__synthesis__',
domain: 'synthesis',
resultCount: response.synthesis.sourceCount,
durationMs: response.diagnostics?.durationMs,
});
}
this.searchClient.recordAnalytics(events);
}

View File

@@ -1,3 +1,52 @@
export interface SearchRouteMatrixEntry {
domain: 'knowledge' | 'findings' | 'policy' | 'vex' | 'platform';
sourceRoute: string;
expectedRoute: string;
}
// Acceptance matrix for FE-112 route normalization coverage.
export const SEARCH_ACTION_ROUTE_MATRIX: ReadonlyArray<SearchRouteMatrixEntry> = [
{
domain: 'knowledge',
sourceRoute: '/docs/modules/platform/architecture-overview.md#release-flow',
expectedRoute: '/docs/modules/platform/architecture-overview.md#release-flow',
},
{
domain: 'findings',
sourceRoute: '/triage/findings/fnd-123',
expectedRoute: '/security/findings/fnd-123',
},
{
domain: 'policy',
sourceRoute: '/policy/DENY-CRITICAL-PROD',
expectedRoute: '/ops/policy?q=DENY-CRITICAL-PROD',
},
{
domain: 'vex',
sourceRoute: '/vex-hub/CVE-2024-21626',
expectedRoute: '/security/advisories-vex?q=CVE-2024-21626',
},
{
domain: 'platform',
sourceRoute: '/scans/scan-42',
expectedRoute: '/security/triage?q=scan-42',
},
];
const KNOWN_FRONTEND_ROUTE_ROOTS: ReadonlyArray<string> = [
'/mission-control',
'/releases',
'/security',
'/evidence',
'/ops',
'/setup',
'/settings',
'/welcome',
'/docs',
'/console',
'/auth',
];
export function normalizeSearchActionRoute(route: string): string {
if (!route.startsWith('/')) {
return route;
@@ -7,7 +56,7 @@ export function normalizeSearchActionRoute(route: string): string {
try {
parsedUrl = new URL(route, 'https://stellaops.local');
} catch {
return route;
return '/ops';
}
const pathname = parsedUrl.pathname;
@@ -27,8 +76,19 @@ export function normalizeSearchActionRoute(route: string): string {
parsedUrl.pathname = '/ops/policy';
parsedUrl.search = lookup ? `?q=${encodeURIComponent(lookup)}` : '';
} else if (pathname.startsWith('/scans/')) {
parsedUrl.pathname = `/security/scans/${pathname.substring('/scans/'.length)}`;
const scanId = decodeURIComponent(pathname.substring('/scans/'.length));
parsedUrl.pathname = '/security/triage';
parsedUrl.search = scanId ? `?q=${encodeURIComponent(scanId)}` : '';
}
if (!isKnownFrontendRoute(parsedUrl.pathname)) {
return '/ops';
}
return `${parsedUrl.pathname}${parsedUrl.search}${parsedUrl.hash}`;
}
function isKnownFrontendRoute(pathname: string): boolean {
return KNOWN_FRONTEND_ROUTE_ROOTS.some((root) =>
pathname === root || pathname.startsWith(`${root}/`));
}

View File

@@ -1,6 +1,8 @@
{
"_meta": { "locale": "bg-BG", "description": "Offline fallback bundle for StellaOps Console" },
"_meta": {
"locale": "bg-BG",
"description": "Offline fallback bundle for StellaOps Console"
},
"common.error.generic": "Something went wrong.",
"common.error.not_found": "The requested resource was not found.",
"common.error.unauthorized": "You do not have permission to perform this action.",
@@ -9,12 +11,10 @@
"common.error.timeout": "Request timed out.",
"common.error.server_error": "An internal server error occurred. Please try again later.",
"common.error.network": "Network error. Check your connection.",
"common.validation.required": "This field is required.",
"common.validation.invalid": "Invalid value.",
"common.validation.too_long": "Maximum {max} characters allowed.",
"common.validation.too_short": "Minimum {min} characters required.",
"common.actions.save": "Save",
"common.actions.cancel": "Cancel",
"common.actions.delete": "Delete",
@@ -26,7 +26,6 @@
"common.actions.collapse": "Collapse",
"common.actions.show_more": "Show more",
"common.actions.show_less": "Show less",
"common.status.healthy": "Healthy",
"common.status.degraded": "Degraded",
"common.status.unavailable": "Unavailable",
@@ -39,16 +38,13 @@
"common.status.failed": "Failed",
"common.status.canceled": "Canceled",
"common.status.blocked": "Blocked",
"common.severity.critical": "Critical",
"common.severity.high": "High",
"common.severity.medium": "Medium",
"common.severity.low": "Low",
"common.severity.info": "Info",
"common.severity.none": "None",
"common.time.just_now": "Just now",
"common.ui.loading": "Loading...",
"common.ui.saving": "Saving...",
"common.ui.deleting": "Deleting...",
@@ -57,11 +53,9 @@
"common.ui.offline": "You are offline.",
"common.ui.reconnecting": "Reconnecting...",
"common.ui.back_online": "Back online.",
"ui.loading.skeleton": "Loading...",
"ui.loading.spinner": "Please wait...",
"ui.loading.slow": "This is taking longer than expected...",
"ui.error.generic": "Something went wrong.",
"ui.error.network": "Network error. Check your connection.",
"ui.error.timeout": "Request timed out. Please try again.",
@@ -70,19 +64,16 @@
"ui.error.server_error": "Server error. Please try again later.",
"ui.error.try_again": "Try again",
"ui.error.go_back": "Go back",
"ui.offline.banner": "You're offline.",
"ui.offline.description": "Some features may be unavailable.",
"ui.offline.reconnecting": "Reconnecting...",
"ui.offline.reconnected": "Back online.",
"ui.toast.success": "Success",
"ui.toast.info": "Info",
"ui.toast.warning": "Warning",
"ui.toast.error": "Error",
"ui.toast.dismiss": "Dismiss",
"ui.toast.undo": "Undo",
"ui.actions.save": "Save",
"ui.actions.saving": "Saving...",
"ui.actions.saved": "Saved",
@@ -111,7 +102,6 @@
"ui.actions.sign_in": "Sign in",
"ui.actions.back_to_list": "Back to list",
"ui.actions.load_more": "Load more",
"ui.labels.all": "All",
"ui.labels.title": "Title",
"ui.labels.description": "Description",
@@ -131,14 +121,12 @@
"ui.labels.selected": "selected",
"ui.labels.last_updated": "Last updated:",
"ui.labels.expires": "Expires",
"ui.validation.required": "This field is required.",
"ui.validation.invalid": "Invalid value.",
"ui.validation.too_long": "Maximum {max} characters allowed.",
"ui.validation.too_short": "Minimum {min} characters required.",
"ui.validation.invalid_email": "Please enter a valid email address.",
"ui.validation.invalid_url": "Please enter a valid URL.",
"ui.a11y.loading": "Content is loading.",
"ui.a11y.loaded": "Content loaded.",
"ui.a11y.error": "An error occurred.",
@@ -148,10 +136,8 @@
"ui.a11y.deselected": "Deselected",
"ui.a11y.required": "Required field",
"ui.a11y.optional": "Optional",
"ui.motion.reduced": "Animations reduced.",
"ui.motion.enabled": "Animations enabled.",
"ui.auth.fresh_active": "Fresh auth: Active",
"ui.auth.fresh_stale": "Fresh auth: Stale",
"ui.locale.label": "Ezik",
@@ -171,15 +157,14 @@
"ui.settings.language.persisted": "Zapazeno za vashiya akaunt i preizpolzvano ot CLI.",
"ui.settings.language.persisted_error": "Zapazeno lokalno, no sinkhronizatsiyata na akaunta se provali.",
"ui.settings.language.sign_in_hint": "Vlezte v sistema, za da sinkhronizirate tazi nastroika s CLI.",
"ui.first_signal.label": "First signal",
"ui.first_signal.run_prefix": "Run:",
"ui.first_signal.live": "Live",
"ui.first_signal.polling": "Polling",
"ui.first_signal.range_prefix": "Range",
"ui.first_signal.range_separator": "\u2013",
"ui.first_signal.stage_separator": " \u00b7 ",
"ui.first_signal.waiting": "Waiting for first signal\u2026",
"ui.first_signal.range_separator": "",
"ui.first_signal.stage_separator": " · ",
"ui.first_signal.waiting": "Waiting for first signal",
"ui.first_signal.not_available": "Signal not available yet.",
"ui.first_signal.offline": "Offline. Last known signal may be stale.",
"ui.first_signal.failed": "Failed to load signal.",
@@ -202,20 +187,17 @@
"ui.first_signal.stage.report": "Generating report",
"ui.first_signal.stage.unknown": "Processing",
"ui.first_signal.aria.card_label": "First signal status",
"ui.severity.critical": "Critical",
"ui.severity.high": "High",
"ui.severity.medium": "Medium",
"ui.severity.low": "Low",
"ui.severity.info": "Info",
"ui.severity.none": "None",
"ui.release_orchestrator.title": "Release Orchestrator",
"ui.release_orchestrator.subtitle": "Pipeline overview and release management",
"ui.release_orchestrator.pipeline_runs": "Pipeline Runs",
"ui.release_orchestrator.refresh_dashboard": "Refresh dashboard",
"ui.risk_dashboard.eyebrow": "Gateway \u00b7 Risk",
"ui.risk_dashboard.eyebrow": "Gateway · Risk",
"ui.risk_dashboard.title": "Risk Profiles",
"ui.risk_dashboard.subtitle": "Tenant-scoped risk posture with deterministic ordering.",
"ui.risk_dashboard.up_to_date": "Up to date",
@@ -225,8 +207,7 @@
"ui.risk_dashboard.risks_suffix": "risks.",
"ui.risk_dashboard.error_unable_to_load": "Unable to load risk profiles.",
"ui.risk_dashboard.no_risks_found": "No risks found for current filters.",
"ui.risk_dashboard.loading_risks": "Loading risks\u2026",
"ui.risk_dashboard.loading_risks": "Loading risks",
"ui.findings.title": "Findings",
"ui.findings.search_placeholder": "Search findings...",
"ui.findings.clear_filters": "Clear Filters",
@@ -242,7 +223,6 @@
"ui.findings.select": "Select",
"ui.findings.no_findings": "No findings to display.",
"ui.findings.no_match": "No findings match the current filters.",
"ui.sources_dashboard.title": "Sources Dashboard",
"ui.sources_dashboard.verifying": "Verifying...",
"ui.sources_dashboard.verify_24h": "Verify last 24h",
@@ -269,7 +249,6 @@
"ui.sources_dashboard.data_from": "Data from",
"ui.sources_dashboard.to": "to",
"ui.sources_dashboard.hour_window": "h window",
"ui.timeline.title": "Timeline",
"ui.timeline.event_timeline": "Event Timeline",
"ui.timeline.refresh_timeline": "Refresh timeline",
@@ -280,7 +259,6 @@
"ui.timeline.load_more": "Load more events",
"ui.timeline.event_details": "Event details",
"ui.timeline.events": "events",
"ui.exception_center.title": "Exception Center",
"ui.exception_center.list_view": "List view",
"ui.exception_center.kanban_view": "Kanban view",
@@ -298,7 +276,6 @@
"ui.exception_center.no_exceptions": "No exceptions match the current filters",
"ui.exception_center.column_empty": "No exceptions",
"ui.exception_center.exceptions_suffix": "exceptions",
"ui.evidence_thread.back_to_list": "Back to list",
"ui.evidence_thread.title_default": "Evidence Thread",
"ui.evidence_thread.copy_digest": "Copy full digest",
@@ -309,7 +286,6 @@
"ui.evidence_thread.timeline_tab": "Timeline",
"ui.evidence_thread.transcript_tab": "Transcript",
"ui.evidence_thread.not_found": "No evidence thread found for this artifact.",
"ui.vulnerability_detail.eyebrow": "Vulnerability",
"ui.vulnerability_detail.cvss": "CVSS",
"ui.vulnerability_detail.impact_first": "Impact First",
@@ -328,5 +304,76 @@
"ui.vulnerability_detail.evidence_tree": "Evidence Tree and Citation Links",
"ui.vulnerability_detail.evidence_explorer": "evidence explorer",
"ui.vulnerability_detail.references": "References",
"ui.vulnerability_detail.back_to_risk": "Back to Risk"
"ui.vulnerability_detail.back_to_risk": "Back to Risk",
"ui.search.input_aria_label": "Global search",
"ui.search.loading": "Searching...",
"ui.search.no_results": "No results found",
"ui.search.did_you_mean_label": "Did you mean:",
"ui.search.try_also_label": "Try also:",
"ui.search.recent_label": "Recent",
"ui.search.clear_history": "Clear",
"ui.search.suggested_label": "Suggested",
"ui.search.empty_state_header": "Search across your release control plane",
"ui.search.quick_action.getting_started": "Getting Started",
"ui.search.quick_action.run_health_check": "Run Health Check",
"ui.search.quick_action.view_recent_scans": "View Recent Scans",
"ui.search.filter.all": "All",
"ui.search.placeholder.default": "Search everything...",
"ui.search.placeholder.try": "Try: {suggestion}",
"ui.search.domain.findings.title": "Security Findings",
"ui.search.domain.findings.description": "CVEs, vulnerabilities, and exposure data across your images.",
"ui.search.domain.findings.example": "CVE-2024-21626",
"ui.search.domain.vex.title": "VEX Statements",
"ui.search.domain.vex.description": "Exploitability statuses and vendor assertions for vulnerabilities.",
"ui.search.domain.vex.example": "not_affected",
"ui.search.domain.policy.title": "Policy Rules",
"ui.search.domain.policy.description": "Release gate rules, exceptions, and enforcement outcomes.",
"ui.search.domain.policy.example": "DENY-CRITICAL-PROD",
"ui.search.domain.docs.title": "Documentation",
"ui.search.domain.docs.description": "Guides, architecture references, and operator runbooks.",
"ui.search.domain.docs.example": "how to deploy",
"ui.search.domain.api.title": "API Reference",
"ui.search.domain.api.description": "OpenAPI endpoints, request contracts, and service operations.",
"ui.search.domain.api.example": "POST /api/v1/scanner/scans",
"ui.search.domain.health.title": "Health Checks",
"ui.search.domain.health.description": "Doctor checks, readiness diagnostics, and remediation guidance.",
"ui.search.domain.health.example": "database connectivity",
"ui.search.domain.operations.title": "Operations",
"ui.search.domain.operations.description": "Jobs, workflows, and operational controls across environments.",
"ui.search.domain.operations.example": "blocked releases",
"ui.search.domain.timeline.title": "Timeline",
"ui.search.domain.timeline.description": "Promotion history, incidents, and causal event traces.",
"ui.search.domain.timeline.example": "release history",
"ui.search.suggestion.findings.critical": "critical findings",
"ui.search.suggestion.findings.reachable": "reachable vulnerabilities",
"ui.search.suggestion.findings.unresolved": "unresolved CVEs",
"ui.search.suggestion.policy.failing_gates": "failing policy gates",
"ui.search.suggestion.policy.production_deny": "production deny rules",
"ui.search.suggestion.policy.exceptions": "policy exceptions",
"ui.search.suggestion.doctor.database": "database connectivity",
"ui.search.suggestion.doctor.disk": "disk space",
"ui.search.suggestion.doctor.oidc": "OIDC readiness",
"ui.search.suggestion.timeline.failed_deployments": "failed deployments",
"ui.search.suggestion.timeline.recent_promotions": "recent promotions",
"ui.search.suggestion.timeline.release_history": "release history",
"ui.search.suggestion.releases.pending_approvals": "pending approvals",
"ui.search.suggestion.releases.blocked_releases": "blocked releases",
"ui.search.suggestion.releases.environment_status": "environment status",
"ui.search.suggestion.default.deploy": "How do I deploy?",
"ui.search.suggestion.default.vex": "What is a VEX statement?",
"ui.search.suggestion.default.critical": "Show critical findings",
"ui.chat.input.waiting": "Waiting for response...",
"ui.chat.input.placeholder": "Ask AdvisoryAI about this finding...",
"ui.chat.suggestion.vulnerability.exploitable": "Is this exploitable in my environment?",
"ui.chat.suggestion.vulnerability.remediation": "What is the remediation?",
"ui.chat.suggestion.vulnerability.evidence_chain": "Show me the evidence chain",
"ui.chat.suggestion.vulnerability.draft_vex": "Draft a VEX statement",
"ui.chat.suggestion.policy.explain_rule": "Explain this policy rule",
"ui.chat.suggestion.policy.override_gate": "What would happen if I override this gate?",
"ui.chat.suggestion.policy.recent_violations": "Show me recent policy violations",
"ui.chat.suggestion.policy.add_exception": "How do I add an exception?",
"ui.chat.suggestion.default.what_can_do": "What can Stella Ops do?",
"ui.chat.suggestion.default.first_scan": "How do I set up my first scan?",
"ui.chat.suggestion.default.promotion_workflow": "Explain the release promotion workflow",
"ui.chat.suggestion.default.health_checks": "What health checks should I run first?"
}

View File

@@ -1,6 +1,8 @@
{
"_meta": { "locale": "de-DE", "description": "Offline fallback bundle for StellaOps Console" },
"_meta": {
"locale": "de-DE",
"description": "Offline fallback bundle for StellaOps Console"
},
"common.error.generic": "Etwas ist schiefgelaufen.",
"common.error.not_found": "The requested resource was not found.",
"common.error.unauthorized": "You do not have permission to perform this action.",
@@ -9,12 +11,10 @@
"common.error.timeout": "Request timed out.",
"common.error.server_error": "An internal server error occurred. Please try again later.",
"common.error.network": "Network error. Check your connection.",
"common.validation.required": "This field is required.",
"common.validation.invalid": "Invalid value.",
"common.validation.too_long": "Maximum {max} characters allowed.",
"common.validation.too_short": "Minimum {min} characters required.",
"common.actions.save": "Speichern",
"common.actions.cancel": "Abbrechen",
"common.actions.delete": "Delete",
@@ -26,7 +26,6 @@
"common.actions.collapse": "Collapse",
"common.actions.show_more": "Show more",
"common.actions.show_less": "Show less",
"common.status.healthy": "Healthy",
"common.status.degraded": "Degraded",
"common.status.unavailable": "Unavailable",
@@ -39,16 +38,13 @@
"common.status.failed": "Failed",
"common.status.canceled": "Canceled",
"common.status.blocked": "Blocked",
"common.severity.critical": "Critical",
"common.severity.high": "High",
"common.severity.medium": "Medium",
"common.severity.low": "Low",
"common.severity.info": "Info",
"common.severity.none": "None",
"common.time.just_now": "Just now",
"common.ui.loading": "Loading...",
"common.ui.saving": "Saving...",
"common.ui.deleting": "Deleting...",
@@ -57,11 +53,9 @@
"common.ui.offline": "You are offline.",
"common.ui.reconnecting": "Reconnecting...",
"common.ui.back_online": "Back online.",
"ui.loading.skeleton": "Loading...",
"ui.loading.spinner": "Please wait...",
"ui.loading.slow": "This is taking longer than expected...",
"ui.error.generic": "Something went wrong.",
"ui.error.network": "Network error. Check your connection.",
"ui.error.timeout": "Request timed out. Please try again.",
@@ -70,19 +64,16 @@
"ui.error.server_error": "Server error. Please try again later.",
"ui.error.try_again": "Try again",
"ui.error.go_back": "Go back",
"ui.offline.banner": "You're offline.",
"ui.offline.description": "Some features may be unavailable.",
"ui.offline.reconnecting": "Reconnecting...",
"ui.offline.reconnected": "Back online.",
"ui.toast.success": "Success",
"ui.toast.info": "Info",
"ui.toast.warning": "Warning",
"ui.toast.error": "Error",
"ui.toast.dismiss": "Dismiss",
"ui.toast.undo": "Undo",
"ui.actions.save": "Save",
"ui.actions.saving": "Saving...",
"ui.actions.saved": "Saved",
@@ -111,7 +102,6 @@
"ui.actions.sign_in": "Sign in",
"ui.actions.back_to_list": "Back to list",
"ui.actions.load_more": "Load more",
"ui.labels.all": "All",
"ui.labels.title": "Title",
"ui.labels.description": "Description",
@@ -131,14 +121,12 @@
"ui.labels.selected": "selected",
"ui.labels.last_updated": "Last updated:",
"ui.labels.expires": "Expires",
"ui.validation.required": "This field is required.",
"ui.validation.invalid": "Invalid value.",
"ui.validation.too_long": "Maximum {max} characters allowed.",
"ui.validation.too_short": "Minimum {min} characters required.",
"ui.validation.invalid_email": "Please enter a valid email address.",
"ui.validation.invalid_url": "Please enter a valid URL.",
"ui.a11y.loading": "Content is loading.",
"ui.a11y.loaded": "Content loaded.",
"ui.a11y.error": "An error occurred.",
@@ -148,10 +136,8 @@
"ui.a11y.deselected": "Deselected",
"ui.a11y.required": "Required field",
"ui.a11y.optional": "Optional",
"ui.motion.reduced": "Animations reduced.",
"ui.motion.enabled": "Animations enabled.",
"ui.auth.fresh_active": "Fresh auth: Active",
"ui.auth.fresh_stale": "Fresh auth: Stale",
"ui.locale.label": "Sprache",
@@ -171,15 +157,14 @@
"ui.settings.language.persisted": "Fuer Ihr Konto gespeichert und im CLI wiederverwendet.",
"ui.settings.language.persisted_error": "Lokal gespeichert, aber Kontosynchronisierung fehlgeschlagen.",
"ui.settings.language.sign_in_hint": "Melden Sie sich an, um diese Einstellung mit dem CLI zu synchronisieren.",
"ui.first_signal.label": "First signal",
"ui.first_signal.run_prefix": "Run:",
"ui.first_signal.live": "Live",
"ui.first_signal.polling": "Polling",
"ui.first_signal.range_prefix": "Range",
"ui.first_signal.range_separator": "\u2013",
"ui.first_signal.stage_separator": " \u00b7 ",
"ui.first_signal.waiting": "Warten auf erstes Signal\u2026",
"ui.first_signal.range_separator": "",
"ui.first_signal.stage_separator": " · ",
"ui.first_signal.waiting": "Warten auf erstes Signal",
"ui.first_signal.not_available": "Signal not available yet.",
"ui.first_signal.offline": "Offline. Last known signal may be stale.",
"ui.first_signal.failed": "Signal konnte nicht geladen werden.",
@@ -202,20 +187,17 @@
"ui.first_signal.stage.report": "Generating report",
"ui.first_signal.stage.unknown": "Processing",
"ui.first_signal.aria.card_label": "First signal status",
"ui.severity.critical": "Critical",
"ui.severity.high": "High",
"ui.severity.medium": "Medium",
"ui.severity.low": "Low",
"ui.severity.info": "Info",
"ui.severity.none": "None",
"ui.release_orchestrator.title": "Release Orchestrator",
"ui.release_orchestrator.subtitle": "Pipeline overview and release management",
"ui.release_orchestrator.pipeline_runs": "Pipeline Runs",
"ui.release_orchestrator.refresh_dashboard": "Refresh dashboard",
"ui.risk_dashboard.eyebrow": "Gateway \u00b7 Risk",
"ui.risk_dashboard.eyebrow": "Gateway · Risk",
"ui.risk_dashboard.title": "Risk Profiles",
"ui.risk_dashboard.subtitle": "Tenant-scoped risk posture with deterministic ordering.",
"ui.risk_dashboard.up_to_date": "Up to date",
@@ -225,8 +207,7 @@
"ui.risk_dashboard.risks_suffix": "risks.",
"ui.risk_dashboard.error_unable_to_load": "Unable to load risk profiles.",
"ui.risk_dashboard.no_risks_found": "No risks found for current filters.",
"ui.risk_dashboard.loading_risks": "Loading risks\u2026",
"ui.risk_dashboard.loading_risks": "Loading risks",
"ui.findings.title": "Findings",
"ui.findings.search_placeholder": "Search findings...",
"ui.findings.clear_filters": "Clear Filters",
@@ -242,7 +223,6 @@
"ui.findings.select": "Select",
"ui.findings.no_findings": "No findings to display.",
"ui.findings.no_match": "No findings match the current filters.",
"ui.sources_dashboard.title": "Sources Dashboard",
"ui.sources_dashboard.verifying": "Verifying...",
"ui.sources_dashboard.verify_24h": "Verify last 24h",
@@ -269,7 +249,6 @@
"ui.sources_dashboard.data_from": "Data from",
"ui.sources_dashboard.to": "to",
"ui.sources_dashboard.hour_window": "h window",
"ui.timeline.title": "Timeline",
"ui.timeline.event_timeline": "Event Timeline",
"ui.timeline.refresh_timeline": "Refresh timeline",
@@ -280,7 +259,6 @@
"ui.timeline.load_more": "Load more events",
"ui.timeline.event_details": "Event details",
"ui.timeline.events": "events",
"ui.exception_center.title": "Exception Center",
"ui.exception_center.list_view": "List view",
"ui.exception_center.kanban_view": "Kanban view",
@@ -298,7 +276,6 @@
"ui.exception_center.no_exceptions": "No exceptions match the current filters",
"ui.exception_center.column_empty": "No exceptions",
"ui.exception_center.exceptions_suffix": "exceptions",
"ui.evidence_thread.back_to_list": "Back to list",
"ui.evidence_thread.title_default": "Evidence Thread",
"ui.evidence_thread.copy_digest": "Copy full digest",
@@ -309,7 +286,6 @@
"ui.evidence_thread.timeline_tab": "Timeline",
"ui.evidence_thread.transcript_tab": "Transcript",
"ui.evidence_thread.not_found": "No evidence thread found for this artifact.",
"ui.vulnerability_detail.eyebrow": "Vulnerability",
"ui.vulnerability_detail.cvss": "CVSS",
"ui.vulnerability_detail.impact_first": "Impact First",
@@ -328,5 +304,76 @@
"ui.vulnerability_detail.evidence_tree": "Evidence Tree and Citation Links",
"ui.vulnerability_detail.evidence_explorer": "evidence explorer",
"ui.vulnerability_detail.references": "References",
"ui.vulnerability_detail.back_to_risk": "Back to Risk"
"ui.vulnerability_detail.back_to_risk": "Back to Risk",
"ui.search.input_aria_label": "Global search",
"ui.search.loading": "Searching...",
"ui.search.no_results": "No results found",
"ui.search.did_you_mean_label": "Did you mean:",
"ui.search.try_also_label": "Try also:",
"ui.search.recent_label": "Recent",
"ui.search.clear_history": "Clear",
"ui.search.suggested_label": "Suggested",
"ui.search.empty_state_header": "Search across your release control plane",
"ui.search.quick_action.getting_started": "Getting Started",
"ui.search.quick_action.run_health_check": "Run Health Check",
"ui.search.quick_action.view_recent_scans": "View Recent Scans",
"ui.search.filter.all": "All",
"ui.search.placeholder.default": "Search everything...",
"ui.search.placeholder.try": "Try: {suggestion}",
"ui.search.domain.findings.title": "Security Findings",
"ui.search.domain.findings.description": "CVEs, vulnerabilities, and exposure data across your images.",
"ui.search.domain.findings.example": "CVE-2024-21626",
"ui.search.domain.vex.title": "VEX Statements",
"ui.search.domain.vex.description": "Exploitability statuses and vendor assertions for vulnerabilities.",
"ui.search.domain.vex.example": "not_affected",
"ui.search.domain.policy.title": "Policy Rules",
"ui.search.domain.policy.description": "Release gate rules, exceptions, and enforcement outcomes.",
"ui.search.domain.policy.example": "DENY-CRITICAL-PROD",
"ui.search.domain.docs.title": "Documentation",
"ui.search.domain.docs.description": "Guides, architecture references, and operator runbooks.",
"ui.search.domain.docs.example": "how to deploy",
"ui.search.domain.api.title": "API Reference",
"ui.search.domain.api.description": "OpenAPI endpoints, request contracts, and service operations.",
"ui.search.domain.api.example": "POST /api/v1/scanner/scans",
"ui.search.domain.health.title": "Health Checks",
"ui.search.domain.health.description": "Doctor checks, readiness diagnostics, and remediation guidance.",
"ui.search.domain.health.example": "database connectivity",
"ui.search.domain.operations.title": "Operations",
"ui.search.domain.operations.description": "Jobs, workflows, and operational controls across environments.",
"ui.search.domain.operations.example": "blocked releases",
"ui.search.domain.timeline.title": "Timeline",
"ui.search.domain.timeline.description": "Promotion history, incidents, and causal event traces.",
"ui.search.domain.timeline.example": "release history",
"ui.search.suggestion.findings.critical": "critical findings",
"ui.search.suggestion.findings.reachable": "reachable vulnerabilities",
"ui.search.suggestion.findings.unresolved": "unresolved CVEs",
"ui.search.suggestion.policy.failing_gates": "failing policy gates",
"ui.search.suggestion.policy.production_deny": "production deny rules",
"ui.search.suggestion.policy.exceptions": "policy exceptions",
"ui.search.suggestion.doctor.database": "database connectivity",
"ui.search.suggestion.doctor.disk": "disk space",
"ui.search.suggestion.doctor.oidc": "OIDC readiness",
"ui.search.suggestion.timeline.failed_deployments": "failed deployments",
"ui.search.suggestion.timeline.recent_promotions": "recent promotions",
"ui.search.suggestion.timeline.release_history": "release history",
"ui.search.suggestion.releases.pending_approvals": "pending approvals",
"ui.search.suggestion.releases.blocked_releases": "blocked releases",
"ui.search.suggestion.releases.environment_status": "environment status",
"ui.search.suggestion.default.deploy": "How do I deploy?",
"ui.search.suggestion.default.vex": "What is a VEX statement?",
"ui.search.suggestion.default.critical": "Show critical findings",
"ui.chat.input.waiting": "Waiting for response...",
"ui.chat.input.placeholder": "Ask AdvisoryAI about this finding...",
"ui.chat.suggestion.vulnerability.exploitable": "Is this exploitable in my environment?",
"ui.chat.suggestion.vulnerability.remediation": "What is the remediation?",
"ui.chat.suggestion.vulnerability.evidence_chain": "Show me the evidence chain",
"ui.chat.suggestion.vulnerability.draft_vex": "Draft a VEX statement",
"ui.chat.suggestion.policy.explain_rule": "Explain this policy rule",
"ui.chat.suggestion.policy.override_gate": "What would happen if I override this gate?",
"ui.chat.suggestion.policy.recent_violations": "Show me recent policy violations",
"ui.chat.suggestion.policy.add_exception": "How do I add an exception?",
"ui.chat.suggestion.default.what_can_do": "What can Stella Ops do?",
"ui.chat.suggestion.default.first_scan": "How do I set up my first scan?",
"ui.chat.suggestion.default.promotion_workflow": "Explain the release promotion workflow",
"ui.chat.suggestion.default.health_checks": "What health checks should I run first?"
}

View File

@@ -1,6 +1,8 @@
{
"_meta": { "locale": "en-US", "description": "Offline fallback bundle for StellaOps Console" },
"_meta": {
"locale": "en-US",
"description": "Offline fallback bundle for StellaOps Console"
},
"common.error.generic": "Something went wrong.",
"common.error.not_found": "The requested resource was not found.",
"common.error.unauthorized": "You do not have permission to perform this action.",
@@ -9,12 +11,10 @@
"common.error.timeout": "Request timed out.",
"common.error.server_error": "An internal server error occurred. Please try again later.",
"common.error.network": "Network error. Check your connection.",
"common.validation.required": "This field is required.",
"common.validation.invalid": "Invalid value.",
"common.validation.too_long": "Maximum {max} characters allowed.",
"common.validation.too_short": "Minimum {min} characters required.",
"common.actions.save": "Save",
"common.actions.cancel": "Cancel",
"common.actions.delete": "Delete",
@@ -26,7 +26,6 @@
"common.actions.collapse": "Collapse",
"common.actions.show_more": "Show more",
"common.actions.show_less": "Show less",
"common.status.healthy": "Healthy",
"common.status.degraded": "Degraded",
"common.status.unavailable": "Unavailable",
@@ -39,16 +38,13 @@
"common.status.failed": "Failed",
"common.status.canceled": "Canceled",
"common.status.blocked": "Blocked",
"common.severity.critical": "Critical",
"common.severity.high": "High",
"common.severity.medium": "Medium",
"common.severity.low": "Low",
"common.severity.info": "Info",
"common.severity.none": "None",
"common.time.just_now": "Just now",
"common.ui.loading": "Loading...",
"common.ui.saving": "Saving...",
"common.ui.deleting": "Deleting...",
@@ -57,11 +53,9 @@
"common.ui.offline": "You are offline.",
"common.ui.reconnecting": "Reconnecting...",
"common.ui.back_online": "Back online.",
"ui.loading.skeleton": "Loading...",
"ui.loading.spinner": "Please wait...",
"ui.loading.slow": "This is taking longer than expected...",
"ui.error.generic": "Something went wrong.",
"ui.error.network": "Network error. Check your connection.",
"ui.error.timeout": "Request timed out. Please try again.",
@@ -70,19 +64,16 @@
"ui.error.server_error": "Server error. Please try again later.",
"ui.error.try_again": "Try again",
"ui.error.go_back": "Go back",
"ui.offline.banner": "You're offline.",
"ui.offline.description": "Some features may be unavailable.",
"ui.offline.reconnecting": "Reconnecting...",
"ui.offline.reconnected": "Back online.",
"ui.toast.success": "Success",
"ui.toast.info": "Info",
"ui.toast.warning": "Warning",
"ui.toast.error": "Error",
"ui.toast.dismiss": "Dismiss",
"ui.toast.undo": "Undo",
"ui.actions.save": "Save",
"ui.actions.saving": "Saving...",
"ui.actions.saved": "Saved",
@@ -111,7 +102,6 @@
"ui.actions.sign_in": "Sign in",
"ui.actions.back_to_list": "Back to list",
"ui.actions.load_more": "Load more",
"ui.labels.all": "All",
"ui.labels.title": "Title",
"ui.labels.description": "Description",
@@ -131,14 +121,12 @@
"ui.labels.selected": "selected",
"ui.labels.last_updated": "Last updated:",
"ui.labels.expires": "Expires",
"ui.validation.required": "This field is required.",
"ui.validation.invalid": "Invalid value.",
"ui.validation.too_long": "Maximum {max} characters allowed.",
"ui.validation.too_short": "Minimum {min} characters required.",
"ui.validation.invalid_email": "Please enter a valid email address.",
"ui.validation.invalid_url": "Please enter a valid URL.",
"ui.a11y.loading": "Content is loading.",
"ui.a11y.loaded": "Content loaded.",
"ui.a11y.error": "An error occurred.",
@@ -148,10 +136,8 @@
"ui.a11y.deselected": "Deselected",
"ui.a11y.required": "Required field",
"ui.a11y.optional": "Optional",
"ui.motion.reduced": "Animations reduced.",
"ui.motion.enabled": "Animations enabled.",
"ui.auth.fresh_active": "Fresh auth: Active",
"ui.auth.fresh_stale": "Fresh auth: Stale",
"ui.locale.label": "Locale",
@@ -171,15 +157,14 @@
"ui.settings.language.persisted": "Saved for your account and reused by CLI.",
"ui.settings.language.persisted_error": "Saved locally, but account sync failed.",
"ui.settings.language.sign_in_hint": "Sign in to sync this preference with CLI.",
"ui.first_signal.label": "First signal",
"ui.first_signal.run_prefix": "Run:",
"ui.first_signal.live": "Live",
"ui.first_signal.polling": "Polling",
"ui.first_signal.range_prefix": "Range",
"ui.first_signal.range_separator": "\u2013",
"ui.first_signal.stage_separator": " \u00b7 ",
"ui.first_signal.waiting": "Waiting for first signal\u2026",
"ui.first_signal.range_separator": "",
"ui.first_signal.stage_separator": " · ",
"ui.first_signal.waiting": "Waiting for first signal",
"ui.first_signal.not_available": "Signal not available yet.",
"ui.first_signal.offline": "Offline. Last known signal may be stale.",
"ui.first_signal.failed": "Failed to load signal.",
@@ -202,20 +187,17 @@
"ui.first_signal.stage.report": "Generating report",
"ui.first_signal.stage.unknown": "Processing",
"ui.first_signal.aria.card_label": "First signal status",
"ui.severity.critical": "Critical",
"ui.severity.high": "High",
"ui.severity.medium": "Medium",
"ui.severity.low": "Low",
"ui.severity.info": "Info",
"ui.severity.none": "None",
"ui.release_orchestrator.title": "Release Orchestrator",
"ui.release_orchestrator.subtitle": "Pipeline overview and release management",
"ui.release_orchestrator.pipeline_runs": "Pipeline Runs",
"ui.release_orchestrator.refresh_dashboard": "Refresh dashboard",
"ui.risk_dashboard.eyebrow": "Gateway \u00b7 Risk",
"ui.risk_dashboard.eyebrow": "Gateway · Risk",
"ui.risk_dashboard.title": "Risk Profiles",
"ui.risk_dashboard.subtitle": "Tenant-scoped risk posture with deterministic ordering.",
"ui.risk_dashboard.up_to_date": "Up to date",
@@ -225,8 +207,7 @@
"ui.risk_dashboard.risks_suffix": "risks.",
"ui.risk_dashboard.error_unable_to_load": "Unable to load risk profiles.",
"ui.risk_dashboard.no_risks_found": "No risks found for current filters.",
"ui.risk_dashboard.loading_risks": "Loading risks\u2026",
"ui.risk_dashboard.loading_risks": "Loading risks",
"ui.findings.title": "Findings",
"ui.findings.search_placeholder": "Search findings...",
"ui.findings.clear_filters": "Clear Filters",
@@ -242,7 +223,6 @@
"ui.findings.select": "Select",
"ui.findings.no_findings": "No findings to display.",
"ui.findings.no_match": "No findings match the current filters.",
"ui.sources_dashboard.title": "Sources Dashboard",
"ui.sources_dashboard.verifying": "Verifying...",
"ui.sources_dashboard.verify_24h": "Verify last 24h",
@@ -269,7 +249,6 @@
"ui.sources_dashboard.data_from": "Data from",
"ui.sources_dashboard.to": "to",
"ui.sources_dashboard.hour_window": "h window",
"ui.timeline.title": "Timeline",
"ui.timeline.event_timeline": "Event Timeline",
"ui.timeline.refresh_timeline": "Refresh timeline",
@@ -280,7 +259,6 @@
"ui.timeline.load_more": "Load more events",
"ui.timeline.event_details": "Event details",
"ui.timeline.events": "events",
"ui.exception_center.title": "Exception Center",
"ui.exception_center.list_view": "List view",
"ui.exception_center.kanban_view": "Kanban view",
@@ -298,7 +276,6 @@
"ui.exception_center.no_exceptions": "No exceptions match the current filters",
"ui.exception_center.column_empty": "No exceptions",
"ui.exception_center.exceptions_suffix": "exceptions",
"ui.evidence_thread.back_to_list": "Back to list",
"ui.evidence_thread.title_default": "Evidence Thread",
"ui.evidence_thread.copy_digest": "Copy full digest",
@@ -309,7 +286,6 @@
"ui.evidence_thread.timeline_tab": "Timeline",
"ui.evidence_thread.transcript_tab": "Transcript",
"ui.evidence_thread.not_found": "No evidence thread found for this artifact.",
"ui.vulnerability_detail.eyebrow": "Vulnerability",
"ui.vulnerability_detail.cvss": "CVSS",
"ui.vulnerability_detail.impact_first": "Impact First",
@@ -328,5 +304,76 @@
"ui.vulnerability_detail.evidence_tree": "Evidence Tree and Citation Links",
"ui.vulnerability_detail.evidence_explorer": "evidence explorer",
"ui.vulnerability_detail.references": "References",
"ui.vulnerability_detail.back_to_risk": "Back to Risk"
"ui.vulnerability_detail.back_to_risk": "Back to Risk",
"ui.search.input_aria_label": "Global search",
"ui.search.loading": "Searching...",
"ui.search.no_results": "No results found",
"ui.search.did_you_mean_label": "Did you mean:",
"ui.search.try_also_label": "Try also:",
"ui.search.recent_label": "Recent",
"ui.search.clear_history": "Clear",
"ui.search.suggested_label": "Suggested",
"ui.search.empty_state_header": "Search across your release control plane",
"ui.search.quick_action.getting_started": "Getting Started",
"ui.search.quick_action.run_health_check": "Run Health Check",
"ui.search.quick_action.view_recent_scans": "View Recent Scans",
"ui.search.filter.all": "All",
"ui.search.placeholder.default": "Search everything...",
"ui.search.placeholder.try": "Try: {suggestion}",
"ui.search.domain.findings.title": "Security Findings",
"ui.search.domain.findings.description": "CVEs, vulnerabilities, and exposure data across your images.",
"ui.search.domain.findings.example": "CVE-2024-21626",
"ui.search.domain.vex.title": "VEX Statements",
"ui.search.domain.vex.description": "Exploitability statuses and vendor assertions for vulnerabilities.",
"ui.search.domain.vex.example": "not_affected",
"ui.search.domain.policy.title": "Policy Rules",
"ui.search.domain.policy.description": "Release gate rules, exceptions, and enforcement outcomes.",
"ui.search.domain.policy.example": "DENY-CRITICAL-PROD",
"ui.search.domain.docs.title": "Documentation",
"ui.search.domain.docs.description": "Guides, architecture references, and operator runbooks.",
"ui.search.domain.docs.example": "how to deploy",
"ui.search.domain.api.title": "API Reference",
"ui.search.domain.api.description": "OpenAPI endpoints, request contracts, and service operations.",
"ui.search.domain.api.example": "POST /api/v1/scanner/scans",
"ui.search.domain.health.title": "Health Checks",
"ui.search.domain.health.description": "Doctor checks, readiness diagnostics, and remediation guidance.",
"ui.search.domain.health.example": "database connectivity",
"ui.search.domain.operations.title": "Operations",
"ui.search.domain.operations.description": "Jobs, workflows, and operational controls across environments.",
"ui.search.domain.operations.example": "blocked releases",
"ui.search.domain.timeline.title": "Timeline",
"ui.search.domain.timeline.description": "Promotion history, incidents, and causal event traces.",
"ui.search.domain.timeline.example": "release history",
"ui.search.suggestion.findings.critical": "critical findings",
"ui.search.suggestion.findings.reachable": "reachable vulnerabilities",
"ui.search.suggestion.findings.unresolved": "unresolved CVEs",
"ui.search.suggestion.policy.failing_gates": "failing policy gates",
"ui.search.suggestion.policy.production_deny": "production deny rules",
"ui.search.suggestion.policy.exceptions": "policy exceptions",
"ui.search.suggestion.doctor.database": "database connectivity",
"ui.search.suggestion.doctor.disk": "disk space",
"ui.search.suggestion.doctor.oidc": "OIDC readiness",
"ui.search.suggestion.timeline.failed_deployments": "failed deployments",
"ui.search.suggestion.timeline.recent_promotions": "recent promotions",
"ui.search.suggestion.timeline.release_history": "release history",
"ui.search.suggestion.releases.pending_approvals": "pending approvals",
"ui.search.suggestion.releases.blocked_releases": "blocked releases",
"ui.search.suggestion.releases.environment_status": "environment status",
"ui.search.suggestion.default.deploy": "How do I deploy?",
"ui.search.suggestion.default.vex": "What is a VEX statement?",
"ui.search.suggestion.default.critical": "Show critical findings",
"ui.chat.input.waiting": "Waiting for response...",
"ui.chat.input.placeholder": "Ask AdvisoryAI about this finding...",
"ui.chat.suggestion.vulnerability.exploitable": "Is this exploitable in my environment?",
"ui.chat.suggestion.vulnerability.remediation": "What is the remediation?",
"ui.chat.suggestion.vulnerability.evidence_chain": "Show me the evidence chain",
"ui.chat.suggestion.vulnerability.draft_vex": "Draft a VEX statement",
"ui.chat.suggestion.policy.explain_rule": "Explain this policy rule",
"ui.chat.suggestion.policy.override_gate": "What would happen if I override this gate?",
"ui.chat.suggestion.policy.recent_violations": "Show me recent policy violations",
"ui.chat.suggestion.policy.add_exception": "How do I add an exception?",
"ui.chat.suggestion.default.what_can_do": "What can Stella Ops do?",
"ui.chat.suggestion.default.first_scan": "How do I set up my first scan?",
"ui.chat.suggestion.default.promotion_workflow": "Explain the release promotion workflow",
"ui.chat.suggestion.default.health_checks": "What health checks should I run first?"
}

View File

@@ -1,6 +1,8 @@
{
"_meta": { "locale": "es-ES", "description": "Offline fallback bundle for StellaOps Console" },
"_meta": {
"locale": "es-ES",
"description": "Offline fallback bundle for StellaOps Console"
},
"common.error.generic": "Something went wrong.",
"common.error.not_found": "The requested resource was not found.",
"common.error.unauthorized": "You do not have permission to perform this action.",
@@ -9,12 +11,10 @@
"common.error.timeout": "Request timed out.",
"common.error.server_error": "An internal server error occurred. Please try again later.",
"common.error.network": "Network error. Check your connection.",
"common.validation.required": "This field is required.",
"common.validation.invalid": "Invalid value.",
"common.validation.too_long": "Maximum {max} characters allowed.",
"common.validation.too_short": "Minimum {min} characters required.",
"common.actions.save": "Save",
"common.actions.cancel": "Cancel",
"common.actions.delete": "Delete",
@@ -26,7 +26,6 @@
"common.actions.collapse": "Collapse",
"common.actions.show_more": "Show more",
"common.actions.show_less": "Show less",
"common.status.healthy": "Healthy",
"common.status.degraded": "Degraded",
"common.status.unavailable": "Unavailable",
@@ -39,16 +38,13 @@
"common.status.failed": "Failed",
"common.status.canceled": "Canceled",
"common.status.blocked": "Blocked",
"common.severity.critical": "Critical",
"common.severity.high": "High",
"common.severity.medium": "Medium",
"common.severity.low": "Low",
"common.severity.info": "Info",
"common.severity.none": "None",
"common.time.just_now": "Just now",
"common.ui.loading": "Loading...",
"common.ui.saving": "Saving...",
"common.ui.deleting": "Deleting...",
@@ -57,11 +53,9 @@
"common.ui.offline": "You are offline.",
"common.ui.reconnecting": "Reconnecting...",
"common.ui.back_online": "Back online.",
"ui.loading.skeleton": "Loading...",
"ui.loading.spinner": "Please wait...",
"ui.loading.slow": "This is taking longer than expected...",
"ui.error.generic": "Something went wrong.",
"ui.error.network": "Network error. Check your connection.",
"ui.error.timeout": "Request timed out. Please try again.",
@@ -70,19 +64,16 @@
"ui.error.server_error": "Server error. Please try again later.",
"ui.error.try_again": "Try again",
"ui.error.go_back": "Go back",
"ui.offline.banner": "You're offline.",
"ui.offline.description": "Some features may be unavailable.",
"ui.offline.reconnecting": "Reconnecting...",
"ui.offline.reconnected": "Back online.",
"ui.toast.success": "Success",
"ui.toast.info": "Info",
"ui.toast.warning": "Warning",
"ui.toast.error": "Error",
"ui.toast.dismiss": "Dismiss",
"ui.toast.undo": "Undo",
"ui.actions.save": "Save",
"ui.actions.saving": "Saving...",
"ui.actions.saved": "Saved",
@@ -111,7 +102,6 @@
"ui.actions.sign_in": "Sign in",
"ui.actions.back_to_list": "Back to list",
"ui.actions.load_more": "Load more",
"ui.labels.all": "All",
"ui.labels.title": "Title",
"ui.labels.description": "Description",
@@ -131,14 +121,12 @@
"ui.labels.selected": "selected",
"ui.labels.last_updated": "Last updated:",
"ui.labels.expires": "Expires",
"ui.validation.required": "This field is required.",
"ui.validation.invalid": "Invalid value.",
"ui.validation.too_long": "Maximum {max} characters allowed.",
"ui.validation.too_short": "Minimum {min} characters required.",
"ui.validation.invalid_email": "Please enter a valid email address.",
"ui.validation.invalid_url": "Please enter a valid URL.",
"ui.a11y.loading": "Content is loading.",
"ui.a11y.loaded": "Content loaded.",
"ui.a11y.error": "An error occurred.",
@@ -148,10 +136,8 @@
"ui.a11y.deselected": "Deselected",
"ui.a11y.required": "Required field",
"ui.a11y.optional": "Optional",
"ui.motion.reduced": "Animations reduced.",
"ui.motion.enabled": "Animations enabled.",
"ui.auth.fresh_active": "Fresh auth: Active",
"ui.auth.fresh_stale": "Fresh auth: Stale",
"ui.locale.label": "Idioma",
@@ -171,15 +157,14 @@
"ui.settings.language.persisted": "Guardado para tu cuenta y reutilizado por CLI.",
"ui.settings.language.persisted_error": "Guardado localmente, pero fallo la sincronizacion de la cuenta.",
"ui.settings.language.sign_in_hint": "Inicia sesion para sincronizar esta preferencia con CLI.",
"ui.first_signal.label": "First signal",
"ui.first_signal.run_prefix": "Run:",
"ui.first_signal.live": "Live",
"ui.first_signal.polling": "Polling",
"ui.first_signal.range_prefix": "Range",
"ui.first_signal.range_separator": "\u2013",
"ui.first_signal.stage_separator": " \u00b7 ",
"ui.first_signal.waiting": "Waiting for first signal\u2026",
"ui.first_signal.range_separator": "",
"ui.first_signal.stage_separator": " · ",
"ui.first_signal.waiting": "Waiting for first signal",
"ui.first_signal.not_available": "Signal not available yet.",
"ui.first_signal.offline": "Offline. Last known signal may be stale.",
"ui.first_signal.failed": "Failed to load signal.",
@@ -202,20 +187,17 @@
"ui.first_signal.stage.report": "Generating report",
"ui.first_signal.stage.unknown": "Processing",
"ui.first_signal.aria.card_label": "First signal status",
"ui.severity.critical": "Critical",
"ui.severity.high": "High",
"ui.severity.medium": "Medium",
"ui.severity.low": "Low",
"ui.severity.info": "Info",
"ui.severity.none": "None",
"ui.release_orchestrator.title": "Release Orchestrator",
"ui.release_orchestrator.subtitle": "Pipeline overview and release management",
"ui.release_orchestrator.pipeline_runs": "Pipeline Runs",
"ui.release_orchestrator.refresh_dashboard": "Refresh dashboard",
"ui.risk_dashboard.eyebrow": "Gateway \u00b7 Risk",
"ui.risk_dashboard.eyebrow": "Gateway · Risk",
"ui.risk_dashboard.title": "Risk Profiles",
"ui.risk_dashboard.subtitle": "Tenant-scoped risk posture with deterministic ordering.",
"ui.risk_dashboard.up_to_date": "Up to date",
@@ -225,8 +207,7 @@
"ui.risk_dashboard.risks_suffix": "risks.",
"ui.risk_dashboard.error_unable_to_load": "Unable to load risk profiles.",
"ui.risk_dashboard.no_risks_found": "No risks found for current filters.",
"ui.risk_dashboard.loading_risks": "Loading risks\u2026",
"ui.risk_dashboard.loading_risks": "Loading risks",
"ui.findings.title": "Findings",
"ui.findings.search_placeholder": "Search findings...",
"ui.findings.clear_filters": "Clear Filters",
@@ -242,7 +223,6 @@
"ui.findings.select": "Select",
"ui.findings.no_findings": "No findings to display.",
"ui.findings.no_match": "No findings match the current filters.",
"ui.sources_dashboard.title": "Sources Dashboard",
"ui.sources_dashboard.verifying": "Verifying...",
"ui.sources_dashboard.verify_24h": "Verify last 24h",
@@ -269,7 +249,6 @@
"ui.sources_dashboard.data_from": "Data from",
"ui.sources_dashboard.to": "to",
"ui.sources_dashboard.hour_window": "h window",
"ui.timeline.title": "Timeline",
"ui.timeline.event_timeline": "Event Timeline",
"ui.timeline.refresh_timeline": "Refresh timeline",
@@ -280,7 +259,6 @@
"ui.timeline.load_more": "Load more events",
"ui.timeline.event_details": "Event details",
"ui.timeline.events": "events",
"ui.exception_center.title": "Exception Center",
"ui.exception_center.list_view": "List view",
"ui.exception_center.kanban_view": "Kanban view",
@@ -298,7 +276,6 @@
"ui.exception_center.no_exceptions": "No exceptions match the current filters",
"ui.exception_center.column_empty": "No exceptions",
"ui.exception_center.exceptions_suffix": "exceptions",
"ui.evidence_thread.back_to_list": "Back to list",
"ui.evidence_thread.title_default": "Evidence Thread",
"ui.evidence_thread.copy_digest": "Copy full digest",
@@ -309,7 +286,6 @@
"ui.evidence_thread.timeline_tab": "Timeline",
"ui.evidence_thread.transcript_tab": "Transcript",
"ui.evidence_thread.not_found": "No evidence thread found for this artifact.",
"ui.vulnerability_detail.eyebrow": "Vulnerability",
"ui.vulnerability_detail.cvss": "CVSS",
"ui.vulnerability_detail.impact_first": "Impact First",
@@ -328,5 +304,76 @@
"ui.vulnerability_detail.evidence_tree": "Evidence Tree and Citation Links",
"ui.vulnerability_detail.evidence_explorer": "evidence explorer",
"ui.vulnerability_detail.references": "References",
"ui.vulnerability_detail.back_to_risk": "Back to Risk"
"ui.vulnerability_detail.back_to_risk": "Back to Risk",
"ui.search.input_aria_label": "Global search",
"ui.search.loading": "Searching...",
"ui.search.no_results": "No results found",
"ui.search.did_you_mean_label": "Did you mean:",
"ui.search.try_also_label": "Try also:",
"ui.search.recent_label": "Recent",
"ui.search.clear_history": "Clear",
"ui.search.suggested_label": "Suggested",
"ui.search.empty_state_header": "Search across your release control plane",
"ui.search.quick_action.getting_started": "Getting Started",
"ui.search.quick_action.run_health_check": "Run Health Check",
"ui.search.quick_action.view_recent_scans": "View Recent Scans",
"ui.search.filter.all": "All",
"ui.search.placeholder.default": "Search everything...",
"ui.search.placeholder.try": "Try: {suggestion}",
"ui.search.domain.findings.title": "Security Findings",
"ui.search.domain.findings.description": "CVEs, vulnerabilities, and exposure data across your images.",
"ui.search.domain.findings.example": "CVE-2024-21626",
"ui.search.domain.vex.title": "VEX Statements",
"ui.search.domain.vex.description": "Exploitability statuses and vendor assertions for vulnerabilities.",
"ui.search.domain.vex.example": "not_affected",
"ui.search.domain.policy.title": "Policy Rules",
"ui.search.domain.policy.description": "Release gate rules, exceptions, and enforcement outcomes.",
"ui.search.domain.policy.example": "DENY-CRITICAL-PROD",
"ui.search.domain.docs.title": "Documentation",
"ui.search.domain.docs.description": "Guides, architecture references, and operator runbooks.",
"ui.search.domain.docs.example": "how to deploy",
"ui.search.domain.api.title": "API Reference",
"ui.search.domain.api.description": "OpenAPI endpoints, request contracts, and service operations.",
"ui.search.domain.api.example": "POST /api/v1/scanner/scans",
"ui.search.domain.health.title": "Health Checks",
"ui.search.domain.health.description": "Doctor checks, readiness diagnostics, and remediation guidance.",
"ui.search.domain.health.example": "database connectivity",
"ui.search.domain.operations.title": "Operations",
"ui.search.domain.operations.description": "Jobs, workflows, and operational controls across environments.",
"ui.search.domain.operations.example": "blocked releases",
"ui.search.domain.timeline.title": "Timeline",
"ui.search.domain.timeline.description": "Promotion history, incidents, and causal event traces.",
"ui.search.domain.timeline.example": "release history",
"ui.search.suggestion.findings.critical": "critical findings",
"ui.search.suggestion.findings.reachable": "reachable vulnerabilities",
"ui.search.suggestion.findings.unresolved": "unresolved CVEs",
"ui.search.suggestion.policy.failing_gates": "failing policy gates",
"ui.search.suggestion.policy.production_deny": "production deny rules",
"ui.search.suggestion.policy.exceptions": "policy exceptions",
"ui.search.suggestion.doctor.database": "database connectivity",
"ui.search.suggestion.doctor.disk": "disk space",
"ui.search.suggestion.doctor.oidc": "OIDC readiness",
"ui.search.suggestion.timeline.failed_deployments": "failed deployments",
"ui.search.suggestion.timeline.recent_promotions": "recent promotions",
"ui.search.suggestion.timeline.release_history": "release history",
"ui.search.suggestion.releases.pending_approvals": "pending approvals",
"ui.search.suggestion.releases.blocked_releases": "blocked releases",
"ui.search.suggestion.releases.environment_status": "environment status",
"ui.search.suggestion.default.deploy": "How do I deploy?",
"ui.search.suggestion.default.vex": "What is a VEX statement?",
"ui.search.suggestion.default.critical": "Show critical findings",
"ui.chat.input.waiting": "Waiting for response...",
"ui.chat.input.placeholder": "Ask AdvisoryAI about this finding...",
"ui.chat.suggestion.vulnerability.exploitable": "Is this exploitable in my environment?",
"ui.chat.suggestion.vulnerability.remediation": "What is the remediation?",
"ui.chat.suggestion.vulnerability.evidence_chain": "Show me the evidence chain",
"ui.chat.suggestion.vulnerability.draft_vex": "Draft a VEX statement",
"ui.chat.suggestion.policy.explain_rule": "Explain this policy rule",
"ui.chat.suggestion.policy.override_gate": "What would happen if I override this gate?",
"ui.chat.suggestion.policy.recent_violations": "Show me recent policy violations",
"ui.chat.suggestion.policy.add_exception": "How do I add an exception?",
"ui.chat.suggestion.default.what_can_do": "What can Stella Ops do?",
"ui.chat.suggestion.default.first_scan": "How do I set up my first scan?",
"ui.chat.suggestion.default.promotion_workflow": "Explain the release promotion workflow",
"ui.chat.suggestion.default.health_checks": "What health checks should I run first?"
}

View File

@@ -1,6 +1,8 @@
{
"_meta": { "locale": "fr-FR", "description": "Offline fallback bundle for StellaOps Console" },
"_meta": {
"locale": "fr-FR",
"description": "Offline fallback bundle for StellaOps Console"
},
"common.error.generic": "Something went wrong.",
"common.error.not_found": "The requested resource was not found.",
"common.error.unauthorized": "You do not have permission to perform this action.",
@@ -9,12 +11,10 @@
"common.error.timeout": "Request timed out.",
"common.error.server_error": "An internal server error occurred. Please try again later.",
"common.error.network": "Network error. Check your connection.",
"common.validation.required": "This field is required.",
"common.validation.invalid": "Invalid value.",
"common.validation.too_long": "Maximum {max} characters allowed.",
"common.validation.too_short": "Minimum {min} characters required.",
"common.actions.save": "Save",
"common.actions.cancel": "Cancel",
"common.actions.delete": "Delete",
@@ -26,7 +26,6 @@
"common.actions.collapse": "Collapse",
"common.actions.show_more": "Show more",
"common.actions.show_less": "Show less",
"common.status.healthy": "Healthy",
"common.status.degraded": "Degraded",
"common.status.unavailable": "Unavailable",
@@ -39,16 +38,13 @@
"common.status.failed": "Failed",
"common.status.canceled": "Canceled",
"common.status.blocked": "Blocked",
"common.severity.critical": "Critical",
"common.severity.high": "High",
"common.severity.medium": "Medium",
"common.severity.low": "Low",
"common.severity.info": "Info",
"common.severity.none": "None",
"common.time.just_now": "Just now",
"common.ui.loading": "Loading...",
"common.ui.saving": "Saving...",
"common.ui.deleting": "Deleting...",
@@ -57,11 +53,9 @@
"common.ui.offline": "You are offline.",
"common.ui.reconnecting": "Reconnecting...",
"common.ui.back_online": "Back online.",
"ui.loading.skeleton": "Loading...",
"ui.loading.spinner": "Please wait...",
"ui.loading.slow": "This is taking longer than expected...",
"ui.error.generic": "Something went wrong.",
"ui.error.network": "Network error. Check your connection.",
"ui.error.timeout": "Request timed out. Please try again.",
@@ -70,19 +64,16 @@
"ui.error.server_error": "Server error. Please try again later.",
"ui.error.try_again": "Try again",
"ui.error.go_back": "Go back",
"ui.offline.banner": "You're offline.",
"ui.offline.description": "Some features may be unavailable.",
"ui.offline.reconnecting": "Reconnecting...",
"ui.offline.reconnected": "Back online.",
"ui.toast.success": "Success",
"ui.toast.info": "Info",
"ui.toast.warning": "Warning",
"ui.toast.error": "Error",
"ui.toast.dismiss": "Dismiss",
"ui.toast.undo": "Undo",
"ui.actions.save": "Save",
"ui.actions.saving": "Saving...",
"ui.actions.saved": "Saved",
@@ -111,7 +102,6 @@
"ui.actions.sign_in": "Sign in",
"ui.actions.back_to_list": "Back to list",
"ui.actions.load_more": "Load more",
"ui.labels.all": "All",
"ui.labels.title": "Title",
"ui.labels.description": "Description",
@@ -131,14 +121,12 @@
"ui.labels.selected": "selected",
"ui.labels.last_updated": "Last updated:",
"ui.labels.expires": "Expires",
"ui.validation.required": "This field is required.",
"ui.validation.invalid": "Invalid value.",
"ui.validation.too_long": "Maximum {max} characters allowed.",
"ui.validation.too_short": "Minimum {min} characters required.",
"ui.validation.invalid_email": "Please enter a valid email address.",
"ui.validation.invalid_url": "Please enter a valid URL.",
"ui.a11y.loading": "Content is loading.",
"ui.a11y.loaded": "Content loaded.",
"ui.a11y.error": "An error occurred.",
@@ -148,10 +136,8 @@
"ui.a11y.deselected": "Deselected",
"ui.a11y.required": "Required field",
"ui.a11y.optional": "Optional",
"ui.motion.reduced": "Animations reduced.",
"ui.motion.enabled": "Animations enabled.",
"ui.auth.fresh_active": "Fresh auth: Active",
"ui.auth.fresh_stale": "Fresh auth: Stale",
"ui.locale.label": "Langue",
@@ -171,15 +157,14 @@
"ui.settings.language.persisted": "Enregistre pour votre compte et reutilise par le CLI.",
"ui.settings.language.persisted_error": "Enregistre localement, mais la synchronisation du compte a echoue.",
"ui.settings.language.sign_in_hint": "Connectez-vous pour synchroniser cette preference avec le CLI.",
"ui.first_signal.label": "First signal",
"ui.first_signal.run_prefix": "Run:",
"ui.first_signal.live": "Live",
"ui.first_signal.polling": "Polling",
"ui.first_signal.range_prefix": "Range",
"ui.first_signal.range_separator": "\u2013",
"ui.first_signal.stage_separator": " \u00b7 ",
"ui.first_signal.waiting": "Waiting for first signal\u2026",
"ui.first_signal.range_separator": "",
"ui.first_signal.stage_separator": " · ",
"ui.first_signal.waiting": "Waiting for first signal",
"ui.first_signal.not_available": "Signal not available yet.",
"ui.first_signal.offline": "Offline. Last known signal may be stale.",
"ui.first_signal.failed": "Failed to load signal.",
@@ -202,20 +187,17 @@
"ui.first_signal.stage.report": "Generating report",
"ui.first_signal.stage.unknown": "Processing",
"ui.first_signal.aria.card_label": "First signal status",
"ui.severity.critical": "Critical",
"ui.severity.high": "High",
"ui.severity.medium": "Medium",
"ui.severity.low": "Low",
"ui.severity.info": "Info",
"ui.severity.none": "None",
"ui.release_orchestrator.title": "Release Orchestrator",
"ui.release_orchestrator.subtitle": "Pipeline overview and release management",
"ui.release_orchestrator.pipeline_runs": "Pipeline Runs",
"ui.release_orchestrator.refresh_dashboard": "Refresh dashboard",
"ui.risk_dashboard.eyebrow": "Gateway \u00b7 Risk",
"ui.risk_dashboard.eyebrow": "Gateway · Risk",
"ui.risk_dashboard.title": "Risk Profiles",
"ui.risk_dashboard.subtitle": "Tenant-scoped risk posture with deterministic ordering.",
"ui.risk_dashboard.up_to_date": "Up to date",
@@ -225,8 +207,7 @@
"ui.risk_dashboard.risks_suffix": "risks.",
"ui.risk_dashboard.error_unable_to_load": "Unable to load risk profiles.",
"ui.risk_dashboard.no_risks_found": "No risks found for current filters.",
"ui.risk_dashboard.loading_risks": "Loading risks\u2026",
"ui.risk_dashboard.loading_risks": "Loading risks",
"ui.findings.title": "Findings",
"ui.findings.search_placeholder": "Search findings...",
"ui.findings.clear_filters": "Clear Filters",
@@ -242,7 +223,6 @@
"ui.findings.select": "Select",
"ui.findings.no_findings": "No findings to display.",
"ui.findings.no_match": "No findings match the current filters.",
"ui.sources_dashboard.title": "Sources Dashboard",
"ui.sources_dashboard.verifying": "Verifying...",
"ui.sources_dashboard.verify_24h": "Verify last 24h",
@@ -269,7 +249,6 @@
"ui.sources_dashboard.data_from": "Data from",
"ui.sources_dashboard.to": "to",
"ui.sources_dashboard.hour_window": "h window",
"ui.timeline.title": "Timeline",
"ui.timeline.event_timeline": "Event Timeline",
"ui.timeline.refresh_timeline": "Refresh timeline",
@@ -280,7 +259,6 @@
"ui.timeline.load_more": "Load more events",
"ui.timeline.event_details": "Event details",
"ui.timeline.events": "events",
"ui.exception_center.title": "Exception Center",
"ui.exception_center.list_view": "List view",
"ui.exception_center.kanban_view": "Kanban view",
@@ -298,7 +276,6 @@
"ui.exception_center.no_exceptions": "No exceptions match the current filters",
"ui.exception_center.column_empty": "No exceptions",
"ui.exception_center.exceptions_suffix": "exceptions",
"ui.evidence_thread.back_to_list": "Back to list",
"ui.evidence_thread.title_default": "Evidence Thread",
"ui.evidence_thread.copy_digest": "Copy full digest",
@@ -309,7 +286,6 @@
"ui.evidence_thread.timeline_tab": "Timeline",
"ui.evidence_thread.transcript_tab": "Transcript",
"ui.evidence_thread.not_found": "No evidence thread found for this artifact.",
"ui.vulnerability_detail.eyebrow": "Vulnerability",
"ui.vulnerability_detail.cvss": "CVSS",
"ui.vulnerability_detail.impact_first": "Impact First",
@@ -328,5 +304,76 @@
"ui.vulnerability_detail.evidence_tree": "Evidence Tree and Citation Links",
"ui.vulnerability_detail.evidence_explorer": "evidence explorer",
"ui.vulnerability_detail.references": "References",
"ui.vulnerability_detail.back_to_risk": "Back to Risk"
"ui.vulnerability_detail.back_to_risk": "Back to Risk",
"ui.search.input_aria_label": "Global search",
"ui.search.loading": "Searching...",
"ui.search.no_results": "No results found",
"ui.search.did_you_mean_label": "Did you mean:",
"ui.search.try_also_label": "Try also:",
"ui.search.recent_label": "Recent",
"ui.search.clear_history": "Clear",
"ui.search.suggested_label": "Suggested",
"ui.search.empty_state_header": "Search across your release control plane",
"ui.search.quick_action.getting_started": "Getting Started",
"ui.search.quick_action.run_health_check": "Run Health Check",
"ui.search.quick_action.view_recent_scans": "View Recent Scans",
"ui.search.filter.all": "All",
"ui.search.placeholder.default": "Search everything...",
"ui.search.placeholder.try": "Try: {suggestion}",
"ui.search.domain.findings.title": "Security Findings",
"ui.search.domain.findings.description": "CVEs, vulnerabilities, and exposure data across your images.",
"ui.search.domain.findings.example": "CVE-2024-21626",
"ui.search.domain.vex.title": "VEX Statements",
"ui.search.domain.vex.description": "Exploitability statuses and vendor assertions for vulnerabilities.",
"ui.search.domain.vex.example": "not_affected",
"ui.search.domain.policy.title": "Policy Rules",
"ui.search.domain.policy.description": "Release gate rules, exceptions, and enforcement outcomes.",
"ui.search.domain.policy.example": "DENY-CRITICAL-PROD",
"ui.search.domain.docs.title": "Documentation",
"ui.search.domain.docs.description": "Guides, architecture references, and operator runbooks.",
"ui.search.domain.docs.example": "how to deploy",
"ui.search.domain.api.title": "API Reference",
"ui.search.domain.api.description": "OpenAPI endpoints, request contracts, and service operations.",
"ui.search.domain.api.example": "POST /api/v1/scanner/scans",
"ui.search.domain.health.title": "Health Checks",
"ui.search.domain.health.description": "Doctor checks, readiness diagnostics, and remediation guidance.",
"ui.search.domain.health.example": "database connectivity",
"ui.search.domain.operations.title": "Operations",
"ui.search.domain.operations.description": "Jobs, workflows, and operational controls across environments.",
"ui.search.domain.operations.example": "blocked releases",
"ui.search.domain.timeline.title": "Timeline",
"ui.search.domain.timeline.description": "Promotion history, incidents, and causal event traces.",
"ui.search.domain.timeline.example": "release history",
"ui.search.suggestion.findings.critical": "critical findings",
"ui.search.suggestion.findings.reachable": "reachable vulnerabilities",
"ui.search.suggestion.findings.unresolved": "unresolved CVEs",
"ui.search.suggestion.policy.failing_gates": "failing policy gates",
"ui.search.suggestion.policy.production_deny": "production deny rules",
"ui.search.suggestion.policy.exceptions": "policy exceptions",
"ui.search.suggestion.doctor.database": "database connectivity",
"ui.search.suggestion.doctor.disk": "disk space",
"ui.search.suggestion.doctor.oidc": "OIDC readiness",
"ui.search.suggestion.timeline.failed_deployments": "failed deployments",
"ui.search.suggestion.timeline.recent_promotions": "recent promotions",
"ui.search.suggestion.timeline.release_history": "release history",
"ui.search.suggestion.releases.pending_approvals": "pending approvals",
"ui.search.suggestion.releases.blocked_releases": "blocked releases",
"ui.search.suggestion.releases.environment_status": "environment status",
"ui.search.suggestion.default.deploy": "How do I deploy?",
"ui.search.suggestion.default.vex": "What is a VEX statement?",
"ui.search.suggestion.default.critical": "Show critical findings",
"ui.chat.input.waiting": "Waiting for response...",
"ui.chat.input.placeholder": "Ask AdvisoryAI about this finding...",
"ui.chat.suggestion.vulnerability.exploitable": "Is this exploitable in my environment?",
"ui.chat.suggestion.vulnerability.remediation": "What is the remediation?",
"ui.chat.suggestion.vulnerability.evidence_chain": "Show me the evidence chain",
"ui.chat.suggestion.vulnerability.draft_vex": "Draft a VEX statement",
"ui.chat.suggestion.policy.explain_rule": "Explain this policy rule",
"ui.chat.suggestion.policy.override_gate": "What would happen if I override this gate?",
"ui.chat.suggestion.policy.recent_violations": "Show me recent policy violations",
"ui.chat.suggestion.policy.add_exception": "How do I add an exception?",
"ui.chat.suggestion.default.what_can_do": "What can Stella Ops do?",
"ui.chat.suggestion.default.first_scan": "How do I set up my first scan?",
"ui.chat.suggestion.default.promotion_workflow": "Explain the release promotion workflow",
"ui.chat.suggestion.default.health_checks": "What health checks should I run first?"
}

View File

@@ -1,6 +1,8 @@
{
"_meta": { "locale": "ru-RU", "description": "Offline fallback bundle for StellaOps Console" },
"_meta": {
"locale": "ru-RU",
"description": "Offline fallback bundle for StellaOps Console"
},
"common.error.generic": "Something went wrong.",
"common.error.not_found": "The requested resource was not found.",
"common.error.unauthorized": "You do not have permission to perform this action.",
@@ -9,12 +11,10 @@
"common.error.timeout": "Request timed out.",
"common.error.server_error": "An internal server error occurred. Please try again later.",
"common.error.network": "Network error. Check your connection.",
"common.validation.required": "This field is required.",
"common.validation.invalid": "Invalid value.",
"common.validation.too_long": "Maximum {max} characters allowed.",
"common.validation.too_short": "Minimum {min} characters required.",
"common.actions.save": "Save",
"common.actions.cancel": "Cancel",
"common.actions.delete": "Delete",
@@ -26,7 +26,6 @@
"common.actions.collapse": "Collapse",
"common.actions.show_more": "Show more",
"common.actions.show_less": "Show less",
"common.status.healthy": "Healthy",
"common.status.degraded": "Degraded",
"common.status.unavailable": "Unavailable",
@@ -39,16 +38,13 @@
"common.status.failed": "Failed",
"common.status.canceled": "Canceled",
"common.status.blocked": "Blocked",
"common.severity.critical": "Critical",
"common.severity.high": "High",
"common.severity.medium": "Medium",
"common.severity.low": "Low",
"common.severity.info": "Info",
"common.severity.none": "None",
"common.time.just_now": "Just now",
"common.ui.loading": "Loading...",
"common.ui.saving": "Saving...",
"common.ui.deleting": "Deleting...",
@@ -57,11 +53,9 @@
"common.ui.offline": "You are offline.",
"common.ui.reconnecting": "Reconnecting...",
"common.ui.back_online": "Back online.",
"ui.loading.skeleton": "Loading...",
"ui.loading.spinner": "Please wait...",
"ui.loading.slow": "This is taking longer than expected...",
"ui.error.generic": "Something went wrong.",
"ui.error.network": "Network error. Check your connection.",
"ui.error.timeout": "Request timed out. Please try again.",
@@ -70,19 +64,16 @@
"ui.error.server_error": "Server error. Please try again later.",
"ui.error.try_again": "Try again",
"ui.error.go_back": "Go back",
"ui.offline.banner": "You're offline.",
"ui.offline.description": "Some features may be unavailable.",
"ui.offline.reconnecting": "Reconnecting...",
"ui.offline.reconnected": "Back online.",
"ui.toast.success": "Success",
"ui.toast.info": "Info",
"ui.toast.warning": "Warning",
"ui.toast.error": "Error",
"ui.toast.dismiss": "Dismiss",
"ui.toast.undo": "Undo",
"ui.actions.save": "Save",
"ui.actions.saving": "Saving...",
"ui.actions.saved": "Saved",
@@ -111,7 +102,6 @@
"ui.actions.sign_in": "Sign in",
"ui.actions.back_to_list": "Back to list",
"ui.actions.load_more": "Load more",
"ui.labels.all": "All",
"ui.labels.title": "Title",
"ui.labels.description": "Description",
@@ -131,14 +121,12 @@
"ui.labels.selected": "selected",
"ui.labels.last_updated": "Last updated:",
"ui.labels.expires": "Expires",
"ui.validation.required": "This field is required.",
"ui.validation.invalid": "Invalid value.",
"ui.validation.too_long": "Maximum {max} characters allowed.",
"ui.validation.too_short": "Minimum {min} characters required.",
"ui.validation.invalid_email": "Please enter a valid email address.",
"ui.validation.invalid_url": "Please enter a valid URL.",
"ui.a11y.loading": "Content is loading.",
"ui.a11y.loaded": "Content loaded.",
"ui.a11y.error": "An error occurred.",
@@ -148,10 +136,8 @@
"ui.a11y.deselected": "Deselected",
"ui.a11y.required": "Required field",
"ui.a11y.optional": "Optional",
"ui.motion.reduced": "Animations reduced.",
"ui.motion.enabled": "Animations enabled.",
"ui.auth.fresh_active": "Fresh auth: Active",
"ui.auth.fresh_stale": "Fresh auth: Stale",
"ui.locale.label": "Yazyk",
@@ -171,15 +157,14 @@
"ui.settings.language.persisted": "Sohraneno dlya vashego akkaunta i ispolzuetsya v CLI.",
"ui.settings.language.persisted_error": "Lokalno sohraneno, no sinkhronizatsiya akkaunta ne udalas.",
"ui.settings.language.sign_in_hint": "Vypolnite vkhod, chtoby sinkhronizirovat etu nastroiku s CLI.",
"ui.first_signal.label": "First signal",
"ui.first_signal.run_prefix": "Run:",
"ui.first_signal.live": "Live",
"ui.first_signal.polling": "Polling",
"ui.first_signal.range_prefix": "Range",
"ui.first_signal.range_separator": "\u2013",
"ui.first_signal.stage_separator": " \u00b7 ",
"ui.first_signal.waiting": "Waiting for first signal\u2026",
"ui.first_signal.range_separator": "",
"ui.first_signal.stage_separator": " · ",
"ui.first_signal.waiting": "Waiting for first signal",
"ui.first_signal.not_available": "Signal not available yet.",
"ui.first_signal.offline": "Offline. Last known signal may be stale.",
"ui.first_signal.failed": "Failed to load signal.",
@@ -202,20 +187,17 @@
"ui.first_signal.stage.report": "Generating report",
"ui.first_signal.stage.unknown": "Processing",
"ui.first_signal.aria.card_label": "First signal status",
"ui.severity.critical": "Critical",
"ui.severity.high": "High",
"ui.severity.medium": "Medium",
"ui.severity.low": "Low",
"ui.severity.info": "Info",
"ui.severity.none": "None",
"ui.release_orchestrator.title": "Release Orchestrator",
"ui.release_orchestrator.subtitle": "Pipeline overview and release management",
"ui.release_orchestrator.pipeline_runs": "Pipeline Runs",
"ui.release_orchestrator.refresh_dashboard": "Refresh dashboard",
"ui.risk_dashboard.eyebrow": "Gateway \u00b7 Risk",
"ui.risk_dashboard.eyebrow": "Gateway · Risk",
"ui.risk_dashboard.title": "Risk Profiles",
"ui.risk_dashboard.subtitle": "Tenant-scoped risk posture with deterministic ordering.",
"ui.risk_dashboard.up_to_date": "Up to date",
@@ -225,8 +207,7 @@
"ui.risk_dashboard.risks_suffix": "risks.",
"ui.risk_dashboard.error_unable_to_load": "Unable to load risk profiles.",
"ui.risk_dashboard.no_risks_found": "No risks found for current filters.",
"ui.risk_dashboard.loading_risks": "Loading risks\u2026",
"ui.risk_dashboard.loading_risks": "Loading risks",
"ui.findings.title": "Findings",
"ui.findings.search_placeholder": "Search findings...",
"ui.findings.clear_filters": "Clear Filters",
@@ -242,7 +223,6 @@
"ui.findings.select": "Select",
"ui.findings.no_findings": "No findings to display.",
"ui.findings.no_match": "No findings match the current filters.",
"ui.sources_dashboard.title": "Sources Dashboard",
"ui.sources_dashboard.verifying": "Verifying...",
"ui.sources_dashboard.verify_24h": "Verify last 24h",
@@ -269,7 +249,6 @@
"ui.sources_dashboard.data_from": "Data from",
"ui.sources_dashboard.to": "to",
"ui.sources_dashboard.hour_window": "h window",
"ui.timeline.title": "Timeline",
"ui.timeline.event_timeline": "Event Timeline",
"ui.timeline.refresh_timeline": "Refresh timeline",
@@ -280,7 +259,6 @@
"ui.timeline.load_more": "Load more events",
"ui.timeline.event_details": "Event details",
"ui.timeline.events": "events",
"ui.exception_center.title": "Exception Center",
"ui.exception_center.list_view": "List view",
"ui.exception_center.kanban_view": "Kanban view",
@@ -298,7 +276,6 @@
"ui.exception_center.no_exceptions": "No exceptions match the current filters",
"ui.exception_center.column_empty": "No exceptions",
"ui.exception_center.exceptions_suffix": "exceptions",
"ui.evidence_thread.back_to_list": "Back to list",
"ui.evidence_thread.title_default": "Evidence Thread",
"ui.evidence_thread.copy_digest": "Copy full digest",
@@ -309,7 +286,6 @@
"ui.evidence_thread.timeline_tab": "Timeline",
"ui.evidence_thread.transcript_tab": "Transcript",
"ui.evidence_thread.not_found": "No evidence thread found for this artifact.",
"ui.vulnerability_detail.eyebrow": "Vulnerability",
"ui.vulnerability_detail.cvss": "CVSS",
"ui.vulnerability_detail.impact_first": "Impact First",
@@ -328,5 +304,76 @@
"ui.vulnerability_detail.evidence_tree": "Evidence Tree and Citation Links",
"ui.vulnerability_detail.evidence_explorer": "evidence explorer",
"ui.vulnerability_detail.references": "References",
"ui.vulnerability_detail.back_to_risk": "Back to Risk"
"ui.vulnerability_detail.back_to_risk": "Back to Risk",
"ui.search.input_aria_label": "Global search",
"ui.search.loading": "Searching...",
"ui.search.no_results": "No results found",
"ui.search.did_you_mean_label": "Did you mean:",
"ui.search.try_also_label": "Try also:",
"ui.search.recent_label": "Recent",
"ui.search.clear_history": "Clear",
"ui.search.suggested_label": "Suggested",
"ui.search.empty_state_header": "Search across your release control plane",
"ui.search.quick_action.getting_started": "Getting Started",
"ui.search.quick_action.run_health_check": "Run Health Check",
"ui.search.quick_action.view_recent_scans": "View Recent Scans",
"ui.search.filter.all": "All",
"ui.search.placeholder.default": "Search everything...",
"ui.search.placeholder.try": "Try: {suggestion}",
"ui.search.domain.findings.title": "Security Findings",
"ui.search.domain.findings.description": "CVEs, vulnerabilities, and exposure data across your images.",
"ui.search.domain.findings.example": "CVE-2024-21626",
"ui.search.domain.vex.title": "VEX Statements",
"ui.search.domain.vex.description": "Exploitability statuses and vendor assertions for vulnerabilities.",
"ui.search.domain.vex.example": "not_affected",
"ui.search.domain.policy.title": "Policy Rules",
"ui.search.domain.policy.description": "Release gate rules, exceptions, and enforcement outcomes.",
"ui.search.domain.policy.example": "DENY-CRITICAL-PROD",
"ui.search.domain.docs.title": "Documentation",
"ui.search.domain.docs.description": "Guides, architecture references, and operator runbooks.",
"ui.search.domain.docs.example": "how to deploy",
"ui.search.domain.api.title": "API Reference",
"ui.search.domain.api.description": "OpenAPI endpoints, request contracts, and service operations.",
"ui.search.domain.api.example": "POST /api/v1/scanner/scans",
"ui.search.domain.health.title": "Health Checks",
"ui.search.domain.health.description": "Doctor checks, readiness diagnostics, and remediation guidance.",
"ui.search.domain.health.example": "database connectivity",
"ui.search.domain.operations.title": "Operations",
"ui.search.domain.operations.description": "Jobs, workflows, and operational controls across environments.",
"ui.search.domain.operations.example": "blocked releases",
"ui.search.domain.timeline.title": "Timeline",
"ui.search.domain.timeline.description": "Promotion history, incidents, and causal event traces.",
"ui.search.domain.timeline.example": "release history",
"ui.search.suggestion.findings.critical": "critical findings",
"ui.search.suggestion.findings.reachable": "reachable vulnerabilities",
"ui.search.suggestion.findings.unresolved": "unresolved CVEs",
"ui.search.suggestion.policy.failing_gates": "failing policy gates",
"ui.search.suggestion.policy.production_deny": "production deny rules",
"ui.search.suggestion.policy.exceptions": "policy exceptions",
"ui.search.suggestion.doctor.database": "database connectivity",
"ui.search.suggestion.doctor.disk": "disk space",
"ui.search.suggestion.doctor.oidc": "OIDC readiness",
"ui.search.suggestion.timeline.failed_deployments": "failed deployments",
"ui.search.suggestion.timeline.recent_promotions": "recent promotions",
"ui.search.suggestion.timeline.release_history": "release history",
"ui.search.suggestion.releases.pending_approvals": "pending approvals",
"ui.search.suggestion.releases.blocked_releases": "blocked releases",
"ui.search.suggestion.releases.environment_status": "environment status",
"ui.search.suggestion.default.deploy": "How do I deploy?",
"ui.search.suggestion.default.vex": "What is a VEX statement?",
"ui.search.suggestion.default.critical": "Show critical findings",
"ui.chat.input.waiting": "Waiting for response...",
"ui.chat.input.placeholder": "Ask AdvisoryAI about this finding...",
"ui.chat.suggestion.vulnerability.exploitable": "Is this exploitable in my environment?",
"ui.chat.suggestion.vulnerability.remediation": "What is the remediation?",
"ui.chat.suggestion.vulnerability.evidence_chain": "Show me the evidence chain",
"ui.chat.suggestion.vulnerability.draft_vex": "Draft a VEX statement",
"ui.chat.suggestion.policy.explain_rule": "Explain this policy rule",
"ui.chat.suggestion.policy.override_gate": "What would happen if I override this gate?",
"ui.chat.suggestion.policy.recent_violations": "Show me recent policy violations",
"ui.chat.suggestion.policy.add_exception": "How do I add an exception?",
"ui.chat.suggestion.default.what_can_do": "What can Stella Ops do?",
"ui.chat.suggestion.default.first_scan": "How do I set up my first scan?",
"ui.chat.suggestion.default.promotion_workflow": "Explain the release promotion workflow",
"ui.chat.suggestion.default.health_checks": "What health checks should I run first?"
}

View File

@@ -1,6 +1,8 @@
{
"_meta": { "locale": "uk-UA", "description": "Offline fallback bundle for StellaOps Console" },
"_meta": {
"locale": "uk-UA",
"description": "Offline fallback bundle for StellaOps Console"
},
"common.error.generic": "Something went wrong.",
"common.error.not_found": "The requested resource was not found.",
"common.error.unauthorized": "You do not have permission to perform this action.",
@@ -9,12 +11,10 @@
"common.error.timeout": "Request timed out.",
"common.error.server_error": "An internal server error occurred. Please try again later.",
"common.error.network": "Network error. Check your connection.",
"common.validation.required": "This field is required.",
"common.validation.invalid": "Invalid value.",
"common.validation.too_long": "Maximum {max} characters allowed.",
"common.validation.too_short": "Minimum {min} characters required.",
"common.actions.save": "Save",
"common.actions.cancel": "Cancel",
"common.actions.delete": "Delete",
@@ -26,7 +26,6 @@
"common.actions.collapse": "Collapse",
"common.actions.show_more": "Show more",
"common.actions.show_less": "Show less",
"common.status.healthy": "Healthy",
"common.status.degraded": "Degraded",
"common.status.unavailable": "Unavailable",
@@ -39,16 +38,13 @@
"common.status.failed": "Failed",
"common.status.canceled": "Canceled",
"common.status.blocked": "Blocked",
"common.severity.critical": "Critical",
"common.severity.high": "High",
"common.severity.medium": "Medium",
"common.severity.low": "Low",
"common.severity.info": "Info",
"common.severity.none": "None",
"common.time.just_now": "Just now",
"common.ui.loading": "Loading...",
"common.ui.saving": "Saving...",
"common.ui.deleting": "Deleting...",
@@ -57,11 +53,9 @@
"common.ui.offline": "You are offline.",
"common.ui.reconnecting": "Reconnecting...",
"common.ui.back_online": "Back online.",
"ui.loading.skeleton": "Loading...",
"ui.loading.spinner": "Please wait...",
"ui.loading.slow": "This is taking longer than expected...",
"ui.error.generic": "Something went wrong.",
"ui.error.network": "Network error. Check your connection.",
"ui.error.timeout": "Request timed out. Please try again.",
@@ -70,19 +64,16 @@
"ui.error.server_error": "Server error. Please try again later.",
"ui.error.try_again": "Try again",
"ui.error.go_back": "Go back",
"ui.offline.banner": "You're offline.",
"ui.offline.description": "Some features may be unavailable.",
"ui.offline.reconnecting": "Reconnecting...",
"ui.offline.reconnected": "Back online.",
"ui.toast.success": "Success",
"ui.toast.info": "Info",
"ui.toast.warning": "Warning",
"ui.toast.error": "Error",
"ui.toast.dismiss": "Dismiss",
"ui.toast.undo": "Undo",
"ui.actions.save": "Save",
"ui.actions.saving": "Saving...",
"ui.actions.saved": "Saved",
@@ -111,7 +102,6 @@
"ui.actions.sign_in": "Sign in",
"ui.actions.back_to_list": "Back to list",
"ui.actions.load_more": "Load more",
"ui.labels.all": "All",
"ui.labels.title": "Title",
"ui.labels.description": "Description",
@@ -131,14 +121,12 @@
"ui.labels.selected": "selected",
"ui.labels.last_updated": "Last updated:",
"ui.labels.expires": "Expires",
"ui.validation.required": "This field is required.",
"ui.validation.invalid": "Invalid value.",
"ui.validation.too_long": "Maximum {max} characters allowed.",
"ui.validation.too_short": "Minimum {min} characters required.",
"ui.validation.invalid_email": "Please enter a valid email address.",
"ui.validation.invalid_url": "Please enter a valid URL.",
"ui.a11y.loading": "Content is loading.",
"ui.a11y.loaded": "Content loaded.",
"ui.a11y.error": "An error occurred.",
@@ -148,10 +136,8 @@
"ui.a11y.deselected": "Deselected",
"ui.a11y.required": "Required field",
"ui.a11y.optional": "Optional",
"ui.motion.reduced": "Animations reduced.",
"ui.motion.enabled": "Animations enabled.",
"ui.auth.fresh_active": "Fresh auth: Active",
"ui.auth.fresh_stale": "Fresh auth: Stale",
"ui.locale.label": "Mova",
@@ -171,15 +157,14 @@
"ui.settings.language.persisted": "Zberezheno dlia vashoho oblikovoho zapysu ta povtorno vykorystovuietsia v CLI.",
"ui.settings.language.persisted_error": "Lokalno zberezheno, ale synkhronizatsiia oblikovoho zapysu ne vdlasia.",
"ui.settings.language.sign_in_hint": "Uvijdit, shchob synkhronizuvaty tsiu nalashtunku z CLI.",
"ui.first_signal.label": "First signal",
"ui.first_signal.run_prefix": "Run:",
"ui.first_signal.live": "Live",
"ui.first_signal.polling": "Polling",
"ui.first_signal.range_prefix": "Range",
"ui.first_signal.range_separator": "\u2013",
"ui.first_signal.stage_separator": " \u00b7 ",
"ui.first_signal.waiting": "Waiting for first signal\u2026",
"ui.first_signal.range_separator": "",
"ui.first_signal.stage_separator": " · ",
"ui.first_signal.waiting": "Waiting for first signal",
"ui.first_signal.not_available": "Signal not available yet.",
"ui.first_signal.offline": "Offline. Last known signal may be stale.",
"ui.first_signal.failed": "Failed to load signal.",
@@ -202,20 +187,17 @@
"ui.first_signal.stage.report": "Generating report",
"ui.first_signal.stage.unknown": "Processing",
"ui.first_signal.aria.card_label": "First signal status",
"ui.severity.critical": "Critical",
"ui.severity.high": "High",
"ui.severity.medium": "Medium",
"ui.severity.low": "Low",
"ui.severity.info": "Info",
"ui.severity.none": "None",
"ui.release_orchestrator.title": "Release Orchestrator",
"ui.release_orchestrator.subtitle": "Pipeline overview and release management",
"ui.release_orchestrator.pipeline_runs": "Pipeline Runs",
"ui.release_orchestrator.refresh_dashboard": "Refresh dashboard",
"ui.risk_dashboard.eyebrow": "Gateway \u00b7 Risk",
"ui.risk_dashboard.eyebrow": "Gateway · Risk",
"ui.risk_dashboard.title": "Risk Profiles",
"ui.risk_dashboard.subtitle": "Tenant-scoped risk posture with deterministic ordering.",
"ui.risk_dashboard.up_to_date": "Up to date",
@@ -225,8 +207,7 @@
"ui.risk_dashboard.risks_suffix": "risks.",
"ui.risk_dashboard.error_unable_to_load": "Unable to load risk profiles.",
"ui.risk_dashboard.no_risks_found": "No risks found for current filters.",
"ui.risk_dashboard.loading_risks": "Loading risks\u2026",
"ui.risk_dashboard.loading_risks": "Loading risks",
"ui.findings.title": "Findings",
"ui.findings.search_placeholder": "Search findings...",
"ui.findings.clear_filters": "Clear Filters",
@@ -242,7 +223,6 @@
"ui.findings.select": "Select",
"ui.findings.no_findings": "No findings to display.",
"ui.findings.no_match": "No findings match the current filters.",
"ui.sources_dashboard.title": "Sources Dashboard",
"ui.sources_dashboard.verifying": "Verifying...",
"ui.sources_dashboard.verify_24h": "Verify last 24h",
@@ -269,7 +249,6 @@
"ui.sources_dashboard.data_from": "Data from",
"ui.sources_dashboard.to": "to",
"ui.sources_dashboard.hour_window": "h window",
"ui.timeline.title": "Timeline",
"ui.timeline.event_timeline": "Event Timeline",
"ui.timeline.refresh_timeline": "Refresh timeline",
@@ -280,7 +259,6 @@
"ui.timeline.load_more": "Load more events",
"ui.timeline.event_details": "Event details",
"ui.timeline.events": "events",
"ui.exception_center.title": "Exception Center",
"ui.exception_center.list_view": "List view",
"ui.exception_center.kanban_view": "Kanban view",
@@ -298,7 +276,6 @@
"ui.exception_center.no_exceptions": "No exceptions match the current filters",
"ui.exception_center.column_empty": "No exceptions",
"ui.exception_center.exceptions_suffix": "exceptions",
"ui.evidence_thread.back_to_list": "Back to list",
"ui.evidence_thread.title_default": "Evidence Thread",
"ui.evidence_thread.copy_digest": "Copy full digest",
@@ -309,7 +286,6 @@
"ui.evidence_thread.timeline_tab": "Timeline",
"ui.evidence_thread.transcript_tab": "Transcript",
"ui.evidence_thread.not_found": "No evidence thread found for this artifact.",
"ui.vulnerability_detail.eyebrow": "Vulnerability",
"ui.vulnerability_detail.cvss": "CVSS",
"ui.vulnerability_detail.impact_first": "Impact First",
@@ -328,5 +304,76 @@
"ui.vulnerability_detail.evidence_tree": "Evidence Tree and Citation Links",
"ui.vulnerability_detail.evidence_explorer": "evidence explorer",
"ui.vulnerability_detail.references": "References",
"ui.vulnerability_detail.back_to_risk": "Back to Risk"
"ui.vulnerability_detail.back_to_risk": "Back to Risk",
"ui.search.input_aria_label": "Global search",
"ui.search.loading": "Searching...",
"ui.search.no_results": "No results found",
"ui.search.did_you_mean_label": "Did you mean:",
"ui.search.try_also_label": "Try also:",
"ui.search.recent_label": "Recent",
"ui.search.clear_history": "Clear",
"ui.search.suggested_label": "Suggested",
"ui.search.empty_state_header": "Search across your release control plane",
"ui.search.quick_action.getting_started": "Getting Started",
"ui.search.quick_action.run_health_check": "Run Health Check",
"ui.search.quick_action.view_recent_scans": "View Recent Scans",
"ui.search.filter.all": "All",
"ui.search.placeholder.default": "Search everything...",
"ui.search.placeholder.try": "Try: {suggestion}",
"ui.search.domain.findings.title": "Security Findings",
"ui.search.domain.findings.description": "CVEs, vulnerabilities, and exposure data across your images.",
"ui.search.domain.findings.example": "CVE-2024-21626",
"ui.search.domain.vex.title": "VEX Statements",
"ui.search.domain.vex.description": "Exploitability statuses and vendor assertions for vulnerabilities.",
"ui.search.domain.vex.example": "not_affected",
"ui.search.domain.policy.title": "Policy Rules",
"ui.search.domain.policy.description": "Release gate rules, exceptions, and enforcement outcomes.",
"ui.search.domain.policy.example": "DENY-CRITICAL-PROD",
"ui.search.domain.docs.title": "Documentation",
"ui.search.domain.docs.description": "Guides, architecture references, and operator runbooks.",
"ui.search.domain.docs.example": "how to deploy",
"ui.search.domain.api.title": "API Reference",
"ui.search.domain.api.description": "OpenAPI endpoints, request contracts, and service operations.",
"ui.search.domain.api.example": "POST /api/v1/scanner/scans",
"ui.search.domain.health.title": "Health Checks",
"ui.search.domain.health.description": "Doctor checks, readiness diagnostics, and remediation guidance.",
"ui.search.domain.health.example": "database connectivity",
"ui.search.domain.operations.title": "Operations",
"ui.search.domain.operations.description": "Jobs, workflows, and operational controls across environments.",
"ui.search.domain.operations.example": "blocked releases",
"ui.search.domain.timeline.title": "Timeline",
"ui.search.domain.timeline.description": "Promotion history, incidents, and causal event traces.",
"ui.search.domain.timeline.example": "release history",
"ui.search.suggestion.findings.critical": "critical findings",
"ui.search.suggestion.findings.reachable": "reachable vulnerabilities",
"ui.search.suggestion.findings.unresolved": "unresolved CVEs",
"ui.search.suggestion.policy.failing_gates": "failing policy gates",
"ui.search.suggestion.policy.production_deny": "production deny rules",
"ui.search.suggestion.policy.exceptions": "policy exceptions",
"ui.search.suggestion.doctor.database": "database connectivity",
"ui.search.suggestion.doctor.disk": "disk space",
"ui.search.suggestion.doctor.oidc": "OIDC readiness",
"ui.search.suggestion.timeline.failed_deployments": "failed deployments",
"ui.search.suggestion.timeline.recent_promotions": "recent promotions",
"ui.search.suggestion.timeline.release_history": "release history",
"ui.search.suggestion.releases.pending_approvals": "pending approvals",
"ui.search.suggestion.releases.blocked_releases": "blocked releases",
"ui.search.suggestion.releases.environment_status": "environment status",
"ui.search.suggestion.default.deploy": "How do I deploy?",
"ui.search.suggestion.default.vex": "What is a VEX statement?",
"ui.search.suggestion.default.critical": "Show critical findings",
"ui.chat.input.waiting": "Waiting for response...",
"ui.chat.input.placeholder": "Ask AdvisoryAI about this finding...",
"ui.chat.suggestion.vulnerability.exploitable": "Is this exploitable in my environment?",
"ui.chat.suggestion.vulnerability.remediation": "What is the remediation?",
"ui.chat.suggestion.vulnerability.evidence_chain": "Show me the evidence chain",
"ui.chat.suggestion.vulnerability.draft_vex": "Draft a VEX statement",
"ui.chat.suggestion.policy.explain_rule": "Explain this policy rule",
"ui.chat.suggestion.policy.override_gate": "What would happen if I override this gate?",
"ui.chat.suggestion.policy.recent_violations": "Show me recent policy violations",
"ui.chat.suggestion.policy.add_exception": "How do I add an exception?",
"ui.chat.suggestion.default.what_can_do": "What can Stella Ops do?",
"ui.chat.suggestion.default.first_scan": "How do I set up my first scan?",
"ui.chat.suggestion.default.promotion_workflow": "Explain the release promotion workflow",
"ui.chat.suggestion.default.health_checks": "What health checks should I run first?"
}

View File

@@ -1,6 +1,8 @@
{
"_meta": { "locale": "zh-CN", "description": "Offline fallback bundle for StellaOps Console" },
"_meta": {
"locale": "zh-CN",
"description": "Offline fallback bundle for StellaOps Console"
},
"common.error.generic": "Something went wrong.",
"common.error.not_found": "The requested resource was not found.",
"common.error.unauthorized": "You do not have permission to perform this action.",
@@ -9,12 +11,10 @@
"common.error.timeout": "Request timed out.",
"common.error.server_error": "An internal server error occurred. Please try again later.",
"common.error.network": "Network error. Check your connection.",
"common.validation.required": "This field is required.",
"common.validation.invalid": "Invalid value.",
"common.validation.too_long": "Maximum {max} characters allowed.",
"common.validation.too_short": "Minimum {min} characters required.",
"common.actions.save": "Save",
"common.actions.cancel": "Cancel",
"common.actions.delete": "Delete",
@@ -26,7 +26,6 @@
"common.actions.collapse": "Collapse",
"common.actions.show_more": "Show more",
"common.actions.show_less": "Show less",
"common.status.healthy": "Healthy",
"common.status.degraded": "Degraded",
"common.status.unavailable": "Unavailable",
@@ -39,16 +38,13 @@
"common.status.failed": "Failed",
"common.status.canceled": "Canceled",
"common.status.blocked": "Blocked",
"common.severity.critical": "Critical",
"common.severity.high": "High",
"common.severity.medium": "Medium",
"common.severity.low": "Low",
"common.severity.info": "Info",
"common.severity.none": "None",
"common.time.just_now": "Just now",
"common.ui.loading": "Loading...",
"common.ui.saving": "Saving...",
"common.ui.deleting": "Deleting...",
@@ -57,11 +53,9 @@
"common.ui.offline": "You are offline.",
"common.ui.reconnecting": "Reconnecting...",
"common.ui.back_online": "Back online.",
"ui.loading.skeleton": "Loading...",
"ui.loading.spinner": "Please wait...",
"ui.loading.slow": "This is taking longer than expected...",
"ui.error.generic": "Something went wrong.",
"ui.error.network": "Network error. Check your connection.",
"ui.error.timeout": "Request timed out. Please try again.",
@@ -70,19 +64,16 @@
"ui.error.server_error": "Server error. Please try again later.",
"ui.error.try_again": "Try again",
"ui.error.go_back": "Go back",
"ui.offline.banner": "You're offline.",
"ui.offline.description": "Some features may be unavailable.",
"ui.offline.reconnecting": "Reconnecting...",
"ui.offline.reconnected": "Back online.",
"ui.toast.success": "Success",
"ui.toast.info": "Info",
"ui.toast.warning": "Warning",
"ui.toast.error": "Error",
"ui.toast.dismiss": "Dismiss",
"ui.toast.undo": "Undo",
"ui.actions.save": "Save",
"ui.actions.saving": "Saving...",
"ui.actions.saved": "Saved",
@@ -111,7 +102,6 @@
"ui.actions.sign_in": "Sign in",
"ui.actions.back_to_list": "Back to list",
"ui.actions.load_more": "Load more",
"ui.labels.all": "All",
"ui.labels.title": "Title",
"ui.labels.description": "Description",
@@ -131,14 +121,12 @@
"ui.labels.selected": "selected",
"ui.labels.last_updated": "Last updated:",
"ui.labels.expires": "Expires",
"ui.validation.required": "This field is required.",
"ui.validation.invalid": "Invalid value.",
"ui.validation.too_long": "Maximum {max} characters allowed.",
"ui.validation.too_short": "Minimum {min} characters required.",
"ui.validation.invalid_email": "Please enter a valid email address.",
"ui.validation.invalid_url": "Please enter a valid URL.",
"ui.a11y.loading": "Content is loading.",
"ui.a11y.loaded": "Content loaded.",
"ui.a11y.error": "An error occurred.",
@@ -148,10 +136,8 @@
"ui.a11y.deselected": "Deselected",
"ui.a11y.required": "Required field",
"ui.a11y.optional": "Optional",
"ui.motion.reduced": "Animations reduced.",
"ui.motion.enabled": "Animations enabled.",
"ui.auth.fresh_active": "Fresh auth: Active",
"ui.auth.fresh_stale": "Fresh auth: Stale",
"ui.locale.label": "Language",
@@ -171,15 +157,14 @@
"ui.settings.language.persisted": "Yi baocun dao nin de zhanghu bing zai CLI zhong chongyong.",
"ui.settings.language.persisted_error": "Yi ben di baocun, dan zhanghu tongbu shibai.",
"ui.settings.language.sign_in_hint": "Qing denglu yi jiang ci pianhao tongbu dao CLI.",
"ui.first_signal.label": "First signal",
"ui.first_signal.run_prefix": "Run:",
"ui.first_signal.live": "Live",
"ui.first_signal.polling": "Polling",
"ui.first_signal.range_prefix": "Range",
"ui.first_signal.range_separator": "\u2013",
"ui.first_signal.stage_separator": " \u00b7 ",
"ui.first_signal.waiting": "Waiting for first signal\u2026",
"ui.first_signal.range_separator": "",
"ui.first_signal.stage_separator": " · ",
"ui.first_signal.waiting": "Waiting for first signal",
"ui.first_signal.not_available": "Signal not available yet.",
"ui.first_signal.offline": "Offline. Last known signal may be stale.",
"ui.first_signal.failed": "Failed to load signal.",
@@ -202,20 +187,17 @@
"ui.first_signal.stage.report": "Generating report",
"ui.first_signal.stage.unknown": "Processing",
"ui.first_signal.aria.card_label": "First signal status",
"ui.severity.critical": "Critical",
"ui.severity.high": "High",
"ui.severity.medium": "Medium",
"ui.severity.low": "Low",
"ui.severity.info": "Info",
"ui.severity.none": "None",
"ui.release_orchestrator.title": "Release Orchestrator",
"ui.release_orchestrator.subtitle": "Pipeline overview and release management",
"ui.release_orchestrator.pipeline_runs": "Pipeline Runs",
"ui.release_orchestrator.refresh_dashboard": "Refresh dashboard",
"ui.risk_dashboard.eyebrow": "Gateway \u00b7 Risk",
"ui.risk_dashboard.eyebrow": "Gateway · Risk",
"ui.risk_dashboard.title": "Risk Profiles",
"ui.risk_dashboard.subtitle": "Tenant-scoped risk posture with deterministic ordering.",
"ui.risk_dashboard.up_to_date": "Up to date",
@@ -225,8 +207,7 @@
"ui.risk_dashboard.risks_suffix": "risks.",
"ui.risk_dashboard.error_unable_to_load": "Unable to load risk profiles.",
"ui.risk_dashboard.no_risks_found": "No risks found for current filters.",
"ui.risk_dashboard.loading_risks": "Loading risks\u2026",
"ui.risk_dashboard.loading_risks": "Loading risks",
"ui.findings.title": "Findings",
"ui.findings.search_placeholder": "Search findings...",
"ui.findings.clear_filters": "Clear Filters",
@@ -242,7 +223,6 @@
"ui.findings.select": "Select",
"ui.findings.no_findings": "No findings to display.",
"ui.findings.no_match": "No findings match the current filters.",
"ui.sources_dashboard.title": "Sources Dashboard",
"ui.sources_dashboard.verifying": "Verifying...",
"ui.sources_dashboard.verify_24h": "Verify last 24h",
@@ -269,7 +249,6 @@
"ui.sources_dashboard.data_from": "Data from",
"ui.sources_dashboard.to": "to",
"ui.sources_dashboard.hour_window": "h window",
"ui.timeline.title": "Timeline",
"ui.timeline.event_timeline": "Event Timeline",
"ui.timeline.refresh_timeline": "Refresh timeline",
@@ -280,7 +259,6 @@
"ui.timeline.load_more": "Load more events",
"ui.timeline.event_details": "Event details",
"ui.timeline.events": "events",
"ui.exception_center.title": "Exception Center",
"ui.exception_center.list_view": "List view",
"ui.exception_center.kanban_view": "Kanban view",
@@ -298,7 +276,6 @@
"ui.exception_center.no_exceptions": "No exceptions match the current filters",
"ui.exception_center.column_empty": "No exceptions",
"ui.exception_center.exceptions_suffix": "exceptions",
"ui.evidence_thread.back_to_list": "Back to list",
"ui.evidence_thread.title_default": "Evidence Thread",
"ui.evidence_thread.copy_digest": "Copy full digest",
@@ -309,7 +286,6 @@
"ui.evidence_thread.timeline_tab": "Timeline",
"ui.evidence_thread.transcript_tab": "Transcript",
"ui.evidence_thread.not_found": "No evidence thread found for this artifact.",
"ui.vulnerability_detail.eyebrow": "Vulnerability",
"ui.vulnerability_detail.cvss": "CVSS",
"ui.vulnerability_detail.impact_first": "Impact First",
@@ -328,5 +304,76 @@
"ui.vulnerability_detail.evidence_tree": "Evidence Tree and Citation Links",
"ui.vulnerability_detail.evidence_explorer": "evidence explorer",
"ui.vulnerability_detail.references": "References",
"ui.vulnerability_detail.back_to_risk": "Back to Risk"
"ui.vulnerability_detail.back_to_risk": "Back to Risk",
"ui.search.input_aria_label": "Global search",
"ui.search.loading": "Searching...",
"ui.search.no_results": "No results found",
"ui.search.did_you_mean_label": "Did you mean:",
"ui.search.try_also_label": "Try also:",
"ui.search.recent_label": "Recent",
"ui.search.clear_history": "Clear",
"ui.search.suggested_label": "Suggested",
"ui.search.empty_state_header": "Search across your release control plane",
"ui.search.quick_action.getting_started": "Getting Started",
"ui.search.quick_action.run_health_check": "Run Health Check",
"ui.search.quick_action.view_recent_scans": "View Recent Scans",
"ui.search.filter.all": "All",
"ui.search.placeholder.default": "Search everything...",
"ui.search.placeholder.try": "Try: {suggestion}",
"ui.search.domain.findings.title": "Security Findings",
"ui.search.domain.findings.description": "CVEs, vulnerabilities, and exposure data across your images.",
"ui.search.domain.findings.example": "CVE-2024-21626",
"ui.search.domain.vex.title": "VEX Statements",
"ui.search.domain.vex.description": "Exploitability statuses and vendor assertions for vulnerabilities.",
"ui.search.domain.vex.example": "not_affected",
"ui.search.domain.policy.title": "Policy Rules",
"ui.search.domain.policy.description": "Release gate rules, exceptions, and enforcement outcomes.",
"ui.search.domain.policy.example": "DENY-CRITICAL-PROD",
"ui.search.domain.docs.title": "Documentation",
"ui.search.domain.docs.description": "Guides, architecture references, and operator runbooks.",
"ui.search.domain.docs.example": "how to deploy",
"ui.search.domain.api.title": "API Reference",
"ui.search.domain.api.description": "OpenAPI endpoints, request contracts, and service operations.",
"ui.search.domain.api.example": "POST /api/v1/scanner/scans",
"ui.search.domain.health.title": "Health Checks",
"ui.search.domain.health.description": "Doctor checks, readiness diagnostics, and remediation guidance.",
"ui.search.domain.health.example": "database connectivity",
"ui.search.domain.operations.title": "Operations",
"ui.search.domain.operations.description": "Jobs, workflows, and operational controls across environments.",
"ui.search.domain.operations.example": "blocked releases",
"ui.search.domain.timeline.title": "Timeline",
"ui.search.domain.timeline.description": "Promotion history, incidents, and causal event traces.",
"ui.search.domain.timeline.example": "release history",
"ui.search.suggestion.findings.critical": "critical findings",
"ui.search.suggestion.findings.reachable": "reachable vulnerabilities",
"ui.search.suggestion.findings.unresolved": "unresolved CVEs",
"ui.search.suggestion.policy.failing_gates": "failing policy gates",
"ui.search.suggestion.policy.production_deny": "production deny rules",
"ui.search.suggestion.policy.exceptions": "policy exceptions",
"ui.search.suggestion.doctor.database": "database connectivity",
"ui.search.suggestion.doctor.disk": "disk space",
"ui.search.suggestion.doctor.oidc": "OIDC readiness",
"ui.search.suggestion.timeline.failed_deployments": "failed deployments",
"ui.search.suggestion.timeline.recent_promotions": "recent promotions",
"ui.search.suggestion.timeline.release_history": "release history",
"ui.search.suggestion.releases.pending_approvals": "pending approvals",
"ui.search.suggestion.releases.blocked_releases": "blocked releases",
"ui.search.suggestion.releases.environment_status": "environment status",
"ui.search.suggestion.default.deploy": "How do I deploy?",
"ui.search.suggestion.default.vex": "What is a VEX statement?",
"ui.search.suggestion.default.critical": "Show critical findings",
"ui.chat.input.waiting": "Waiting for response...",
"ui.chat.input.placeholder": "Ask AdvisoryAI about this finding...",
"ui.chat.suggestion.vulnerability.exploitable": "Is this exploitable in my environment?",
"ui.chat.suggestion.vulnerability.remediation": "What is the remediation?",
"ui.chat.suggestion.vulnerability.evidence_chain": "Show me the evidence chain",
"ui.chat.suggestion.vulnerability.draft_vex": "Draft a VEX statement",
"ui.chat.suggestion.policy.explain_rule": "Explain this policy rule",
"ui.chat.suggestion.policy.override_gate": "What would happen if I override this gate?",
"ui.chat.suggestion.policy.recent_violations": "Show me recent policy violations",
"ui.chat.suggestion.policy.add_exception": "How do I add an exception?",
"ui.chat.suggestion.default.what_can_do": "What can Stella Ops do?",
"ui.chat.suggestion.default.first_scan": "How do I set up my first scan?",
"ui.chat.suggestion.default.promotion_workflow": "Explain the release promotion workflow",
"ui.chat.suggestion.default.health_checks": "What health checks should I run first?"
}

View File

@@ -1,6 +1,8 @@
{
"_meta": { "locale": "zh-TW", "description": "Offline fallback bundle for StellaOps Console" },
"_meta": {
"locale": "zh-TW",
"description": "Offline fallback bundle for StellaOps Console"
},
"common.error.generic": "Something went wrong.",
"common.error.not_found": "The requested resource was not found.",
"common.error.unauthorized": "You do not have permission to perform this action.",
@@ -9,12 +11,10 @@
"common.error.timeout": "Request timed out.",
"common.error.server_error": "An internal server error occurred. Please try again later.",
"common.error.network": "Network error. Check your connection.",
"common.validation.required": "This field is required.",
"common.validation.invalid": "Invalid value.",
"common.validation.too_long": "Maximum {max} characters allowed.",
"common.validation.too_short": "Minimum {min} characters required.",
"common.actions.save": "Save",
"common.actions.cancel": "Cancel",
"common.actions.delete": "Delete",
@@ -26,7 +26,6 @@
"common.actions.collapse": "Collapse",
"common.actions.show_more": "Show more",
"common.actions.show_less": "Show less",
"common.status.healthy": "Healthy",
"common.status.degraded": "Degraded",
"common.status.unavailable": "Unavailable",
@@ -39,16 +38,13 @@
"common.status.failed": "Failed",
"common.status.canceled": "Canceled",
"common.status.blocked": "Blocked",
"common.severity.critical": "Critical",
"common.severity.high": "High",
"common.severity.medium": "Medium",
"common.severity.low": "Low",
"common.severity.info": "Info",
"common.severity.none": "None",
"common.time.just_now": "Just now",
"common.ui.loading": "Loading...",
"common.ui.saving": "Saving...",
"common.ui.deleting": "Deleting...",
@@ -57,11 +53,9 @@
"common.ui.offline": "You are offline.",
"common.ui.reconnecting": "Reconnecting...",
"common.ui.back_online": "Back online.",
"ui.loading.skeleton": "Loading...",
"ui.loading.spinner": "Please wait...",
"ui.loading.slow": "This is taking longer than expected...",
"ui.error.generic": "Something went wrong.",
"ui.error.network": "Network error. Check your connection.",
"ui.error.timeout": "Request timed out. Please try again.",
@@ -70,19 +64,16 @@
"ui.error.server_error": "Server error. Please try again later.",
"ui.error.try_again": "Try again",
"ui.error.go_back": "Go back",
"ui.offline.banner": "You're offline.",
"ui.offline.description": "Some features may be unavailable.",
"ui.offline.reconnecting": "Reconnecting...",
"ui.offline.reconnected": "Back online.",
"ui.toast.success": "Success",
"ui.toast.info": "Info",
"ui.toast.warning": "Warning",
"ui.toast.error": "Error",
"ui.toast.dismiss": "Dismiss",
"ui.toast.undo": "Undo",
"ui.actions.save": "Save",
"ui.actions.saving": "Saving...",
"ui.actions.saved": "Saved",
@@ -111,7 +102,6 @@
"ui.actions.sign_in": "Sign in",
"ui.actions.back_to_list": "Back to list",
"ui.actions.load_more": "Load more",
"ui.labels.all": "All",
"ui.labels.title": "Title",
"ui.labels.description": "Description",
@@ -131,14 +121,12 @@
"ui.labels.selected": "selected",
"ui.labels.last_updated": "Last updated:",
"ui.labels.expires": "Expires",
"ui.validation.required": "This field is required.",
"ui.validation.invalid": "Invalid value.",
"ui.validation.too_long": "Maximum {max} characters allowed.",
"ui.validation.too_short": "Minimum {min} characters required.",
"ui.validation.invalid_email": "Please enter a valid email address.",
"ui.validation.invalid_url": "Please enter a valid URL.",
"ui.a11y.loading": "Content is loading.",
"ui.a11y.loaded": "Content loaded.",
"ui.a11y.error": "An error occurred.",
@@ -148,10 +136,8 @@
"ui.a11y.deselected": "Deselected",
"ui.a11y.required": "Required field",
"ui.a11y.optional": "Optional",
"ui.motion.reduced": "Animations reduced.",
"ui.motion.enabled": "Animations enabled.",
"ui.auth.fresh_active": "Fresh auth: Active",
"ui.auth.fresh_stale": "Fresh auth: Stale",
"ui.locale.label": "Language",
@@ -171,15 +157,14 @@
"ui.settings.language.persisted": "Yijing baocun dao zhanghu bing gong CLI chongyong.",
"ui.settings.language.persisted_error": "Yijing benji baocun, dan zhanghu tongbu shibai.",
"ui.settings.language.sign_in_hint": "Qing dengru yi jiang ci pianhao tongbu dao CLI.",
"ui.first_signal.label": "First signal",
"ui.first_signal.run_prefix": "Run:",
"ui.first_signal.live": "Live",
"ui.first_signal.polling": "Polling",
"ui.first_signal.range_prefix": "Range",
"ui.first_signal.range_separator": "\u2013",
"ui.first_signal.stage_separator": " \u00b7 ",
"ui.first_signal.waiting": "Waiting for first signal\u2026",
"ui.first_signal.range_separator": "",
"ui.first_signal.stage_separator": " · ",
"ui.first_signal.waiting": "Waiting for first signal",
"ui.first_signal.not_available": "Signal not available yet.",
"ui.first_signal.offline": "Offline. Last known signal may be stale.",
"ui.first_signal.failed": "Failed to load signal.",
@@ -202,20 +187,17 @@
"ui.first_signal.stage.report": "Generating report",
"ui.first_signal.stage.unknown": "Processing",
"ui.first_signal.aria.card_label": "First signal status",
"ui.severity.critical": "Critical",
"ui.severity.high": "High",
"ui.severity.medium": "Medium",
"ui.severity.low": "Low",
"ui.severity.info": "Info",
"ui.severity.none": "None",
"ui.release_orchestrator.title": "Release Orchestrator",
"ui.release_orchestrator.subtitle": "Pipeline overview and release management",
"ui.release_orchestrator.pipeline_runs": "Pipeline Runs",
"ui.release_orchestrator.refresh_dashboard": "Refresh dashboard",
"ui.risk_dashboard.eyebrow": "Gateway \u00b7 Risk",
"ui.risk_dashboard.eyebrow": "Gateway · Risk",
"ui.risk_dashboard.title": "Risk Profiles",
"ui.risk_dashboard.subtitle": "Tenant-scoped risk posture with deterministic ordering.",
"ui.risk_dashboard.up_to_date": "Up to date",
@@ -225,8 +207,7 @@
"ui.risk_dashboard.risks_suffix": "risks.",
"ui.risk_dashboard.error_unable_to_load": "Unable to load risk profiles.",
"ui.risk_dashboard.no_risks_found": "No risks found for current filters.",
"ui.risk_dashboard.loading_risks": "Loading risks\u2026",
"ui.risk_dashboard.loading_risks": "Loading risks",
"ui.findings.title": "Findings",
"ui.findings.search_placeholder": "Search findings...",
"ui.findings.clear_filters": "Clear Filters",
@@ -242,7 +223,6 @@
"ui.findings.select": "Select",
"ui.findings.no_findings": "No findings to display.",
"ui.findings.no_match": "No findings match the current filters.",
"ui.sources_dashboard.title": "Sources Dashboard",
"ui.sources_dashboard.verifying": "Verifying...",
"ui.sources_dashboard.verify_24h": "Verify last 24h",
@@ -269,7 +249,6 @@
"ui.sources_dashboard.data_from": "Data from",
"ui.sources_dashboard.to": "to",
"ui.sources_dashboard.hour_window": "h window",
"ui.timeline.title": "Timeline",
"ui.timeline.event_timeline": "Event Timeline",
"ui.timeline.refresh_timeline": "Refresh timeline",
@@ -280,7 +259,6 @@
"ui.timeline.load_more": "Load more events",
"ui.timeline.event_details": "Event details",
"ui.timeline.events": "events",
"ui.exception_center.title": "Exception Center",
"ui.exception_center.list_view": "List view",
"ui.exception_center.kanban_view": "Kanban view",
@@ -298,7 +276,6 @@
"ui.exception_center.no_exceptions": "No exceptions match the current filters",
"ui.exception_center.column_empty": "No exceptions",
"ui.exception_center.exceptions_suffix": "exceptions",
"ui.evidence_thread.back_to_list": "Back to list",
"ui.evidence_thread.title_default": "Evidence Thread",
"ui.evidence_thread.copy_digest": "Copy full digest",
@@ -309,7 +286,6 @@
"ui.evidence_thread.timeline_tab": "Timeline",
"ui.evidence_thread.transcript_tab": "Transcript",
"ui.evidence_thread.not_found": "No evidence thread found for this artifact.",
"ui.vulnerability_detail.eyebrow": "Vulnerability",
"ui.vulnerability_detail.cvss": "CVSS",
"ui.vulnerability_detail.impact_first": "Impact First",
@@ -328,5 +304,76 @@
"ui.vulnerability_detail.evidence_tree": "Evidence Tree and Citation Links",
"ui.vulnerability_detail.evidence_explorer": "evidence explorer",
"ui.vulnerability_detail.references": "References",
"ui.vulnerability_detail.back_to_risk": "Back to Risk"
"ui.vulnerability_detail.back_to_risk": "Back to Risk",
"ui.search.input_aria_label": "Global search",
"ui.search.loading": "Searching...",
"ui.search.no_results": "No results found",
"ui.search.did_you_mean_label": "Did you mean:",
"ui.search.try_also_label": "Try also:",
"ui.search.recent_label": "Recent",
"ui.search.clear_history": "Clear",
"ui.search.suggested_label": "Suggested",
"ui.search.empty_state_header": "Search across your release control plane",
"ui.search.quick_action.getting_started": "Getting Started",
"ui.search.quick_action.run_health_check": "Run Health Check",
"ui.search.quick_action.view_recent_scans": "View Recent Scans",
"ui.search.filter.all": "All",
"ui.search.placeholder.default": "Search everything...",
"ui.search.placeholder.try": "Try: {suggestion}",
"ui.search.domain.findings.title": "Security Findings",
"ui.search.domain.findings.description": "CVEs, vulnerabilities, and exposure data across your images.",
"ui.search.domain.findings.example": "CVE-2024-21626",
"ui.search.domain.vex.title": "VEX Statements",
"ui.search.domain.vex.description": "Exploitability statuses and vendor assertions for vulnerabilities.",
"ui.search.domain.vex.example": "not_affected",
"ui.search.domain.policy.title": "Policy Rules",
"ui.search.domain.policy.description": "Release gate rules, exceptions, and enforcement outcomes.",
"ui.search.domain.policy.example": "DENY-CRITICAL-PROD",
"ui.search.domain.docs.title": "Documentation",
"ui.search.domain.docs.description": "Guides, architecture references, and operator runbooks.",
"ui.search.domain.docs.example": "how to deploy",
"ui.search.domain.api.title": "API Reference",
"ui.search.domain.api.description": "OpenAPI endpoints, request contracts, and service operations.",
"ui.search.domain.api.example": "POST /api/v1/scanner/scans",
"ui.search.domain.health.title": "Health Checks",
"ui.search.domain.health.description": "Doctor checks, readiness diagnostics, and remediation guidance.",
"ui.search.domain.health.example": "database connectivity",
"ui.search.domain.operations.title": "Operations",
"ui.search.domain.operations.description": "Jobs, workflows, and operational controls across environments.",
"ui.search.domain.operations.example": "blocked releases",
"ui.search.domain.timeline.title": "Timeline",
"ui.search.domain.timeline.description": "Promotion history, incidents, and causal event traces.",
"ui.search.domain.timeline.example": "release history",
"ui.search.suggestion.findings.critical": "critical findings",
"ui.search.suggestion.findings.reachable": "reachable vulnerabilities",
"ui.search.suggestion.findings.unresolved": "unresolved CVEs",
"ui.search.suggestion.policy.failing_gates": "failing policy gates",
"ui.search.suggestion.policy.production_deny": "production deny rules",
"ui.search.suggestion.policy.exceptions": "policy exceptions",
"ui.search.suggestion.doctor.database": "database connectivity",
"ui.search.suggestion.doctor.disk": "disk space",
"ui.search.suggestion.doctor.oidc": "OIDC readiness",
"ui.search.suggestion.timeline.failed_deployments": "failed deployments",
"ui.search.suggestion.timeline.recent_promotions": "recent promotions",
"ui.search.suggestion.timeline.release_history": "release history",
"ui.search.suggestion.releases.pending_approvals": "pending approvals",
"ui.search.suggestion.releases.blocked_releases": "blocked releases",
"ui.search.suggestion.releases.environment_status": "environment status",
"ui.search.suggestion.default.deploy": "How do I deploy?",
"ui.search.suggestion.default.vex": "What is a VEX statement?",
"ui.search.suggestion.default.critical": "Show critical findings",
"ui.chat.input.waiting": "Waiting for response...",
"ui.chat.input.placeholder": "Ask AdvisoryAI about this finding...",
"ui.chat.suggestion.vulnerability.exploitable": "Is this exploitable in my environment?",
"ui.chat.suggestion.vulnerability.remediation": "What is the remediation?",
"ui.chat.suggestion.vulnerability.evidence_chain": "Show me the evidence chain",
"ui.chat.suggestion.vulnerability.draft_vex": "Draft a VEX statement",
"ui.chat.suggestion.policy.explain_rule": "Explain this policy rule",
"ui.chat.suggestion.policy.override_gate": "What would happen if I override this gate?",
"ui.chat.suggestion.policy.recent_violations": "Show me recent policy violations",
"ui.chat.suggestion.policy.add_exception": "How do I add an exception?",
"ui.chat.suggestion.default.what_can_do": "What can Stella Ops do?",
"ui.chat.suggestion.default.first_scan": "How do I set up my first scan?",
"ui.chat.suggestion.default.promotion_workflow": "Explain the release promotion workflow",
"ui.chat.suggestion.default.health_checks": "What health checks should I run first?"
}

View File

@@ -0,0 +1,57 @@
import { TestBed } from '@angular/core/testing';
import { NavigationEnd, Router } from '@angular/router';
import { Subject } from 'rxjs';
import { AmbientContextService } from '../../app/core/services/ambient-context.service';
describe('AmbientContextService', () => {
let events: Subject<unknown>;
let router: { url: string; events: Subject<unknown> };
beforeEach(() => {
events = new Subject<unknown>();
router = {
url: '/security/triage',
events,
};
TestBed.configureTestingModule({
providers: [
AmbientContextService,
{ provide: Router, useValue: router },
],
});
});
it('returns findings domain and findings suggestions for triage routes', () => {
const service = TestBed.inject(AmbientContextService);
expect(service.currentDomain()).toBe('findings');
expect(service.getSearchSuggestions().map((item) => item.key)).toEqual([
'ui.search.suggestion.findings.critical',
'ui.search.suggestion.findings.reachable',
'ui.search.suggestion.findings.unresolved',
]);
});
it('updates search and chat suggestion sets when route changes', () => {
const service = TestBed.inject(AmbientContextService);
router.url = '/ops/policy';
events.next(new NavigationEnd(1, '/ops/policy', '/ops/policy'));
expect(service.currentDomain()).toBe('policy');
expect(service.getSearchSuggestions().map((item) => item.key)).toEqual([
'ui.search.suggestion.policy.failing_gates',
'ui.search.suggestion.policy.production_deny',
'ui.search.suggestion.policy.exceptions',
]);
expect(service.getChatSuggestions().map((item) => item.key)).toEqual([
'ui.chat.suggestion.policy.explain_rule',
'ui.chat.suggestion.policy.override_gate',
'ui.chat.suggestion.policy.recent_violations',
'ui.chat.suggestion.policy.add_exception',
]);
});
});

View File

@@ -2,9 +2,11 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NavigationEnd, Router } from '@angular/router';
import { Subject, of } from 'rxjs';
import type { EntityCard } from '../../app/core/api/unified-search.models';
import { UnifiedSearchClient } from '../../app/core/api/unified-search.client';
import { AmbientContextService } from '../../app/core/services/ambient-context.service';
import { SearchChatContextService } from '../../app/core/services/search-chat-context.service';
import { I18nService } from '../../app/core/i18n';
import { GlobalSearchComponent } from '../../app/layout/global-search/global-search.component';
describe('GlobalSearchComponent', () => {
@@ -63,6 +65,17 @@ describe('GlobalSearchComponent', () => {
provide: AmbientContextService,
useValue: {
buildContextFilter: () => undefined,
getSearchSuggestions: () => [
{ key: 'ui.search.suggestion.default.deploy', fallback: 'How do I deploy?' },
{ key: 'ui.search.suggestion.default.vex', fallback: 'What is a VEX statement?' },
{ key: 'ui.search.suggestion.default.critical', fallback: 'Show critical findings' },
],
},
},
{
provide: I18nService,
useValue: {
tryT: () => null,
},
},
{ provide: SearchChatContextService, useValue: searchChatContext },
@@ -84,6 +97,14 @@ describe('GlobalSearchComponent', () => {
expect(input?.placeholder).toContain('Try:');
});
it('renders eight domain cards in empty state guide', () => {
component.onFocus();
fixture.detectChanges();
const cards = fixture.nativeElement.querySelectorAll('.search__domain-card');
expect(cards.length).toBe(8);
});
it('queries unified search for one-character query terms', async () => {
component.onFocus();
component.onQueryChange('a');
@@ -92,6 +113,43 @@ describe('GlobalSearchComponent', () => {
expect(searchClient.search).toHaveBeenCalledWith('a', undefined);
});
it('records synthesis analytics when synthesis is present in search response', async () => {
searchClient.recordAnalytics.calls.reset();
searchClient.search.and.returnValue(of({
query: 'critical findings',
topK: 10,
cards: [createCard('findings', '/triage/findings/fnd-101')],
synthesis: {
summary: 'Synthesis summary',
template: 'finding_overview',
confidence: 'high',
sourceCount: 1,
domainsCovered: ['findings'],
},
diagnostics: {
ftsMatches: 1,
vectorMatches: 0,
entityCardCount: 1,
durationMs: 11,
usedVector: false,
mode: 'fts-only',
},
}));
component.onFocus();
component.onQueryChange('critical findings');
await waitForDebounce();
const events = searchClient.recordAnalytics.calls.mostRecent().args[0] as Array<Record<string, unknown>>;
const synthesisEvent = events.find((event) => event['eventType'] === 'synthesis');
expect(synthesisEvent).toEqual(jasmine.objectContaining({
eventType: 'synthesis',
entityKey: '__synthesis__',
domain: 'synthesis',
resultCount: 1,
}));
});
it('consumes chat-to-search context when navigation changes', () => {
searchChatContext.consumeChatToSearch.and.returnValue({
query: 'CVE-2024-21626',
@@ -103,4 +161,135 @@ describe('GlobalSearchComponent', () => {
expect(component.query()).toBe('CVE-2024-21626');
});
it('navigates to assistant host with openChat intent from Ask AI card action', () => {
const card = createCard('findings', '/triage/findings/fnd-1');
component.onAskAiFromCard(card);
expect(searchChatContext.setSearchToChat).toHaveBeenCalled();
expect(router.navigate).toHaveBeenCalledWith(
['/security/triage'],
jasmine.objectContaining({
queryParams: jasmine.objectContaining({
openChat: 'true',
}),
}),
);
});
it('normalizes and navigates entity-card actions for all primary domains', () => {
const cases: Array<{ domain: EntityCard['domain']; route: string; expected: string }> = [
{
domain: 'knowledge',
route: '/docs/modules/platform/architecture-overview.md#release-flow',
expected: '/docs/modules/platform/architecture-overview.md#release-flow',
},
{
domain: 'findings',
route: '/triage/findings/fnd-123',
expected: '/security/findings/fnd-123',
},
{
domain: 'policy',
route: '/policy/DENY-CRITICAL-PROD',
expected: '/ops/policy?q=DENY-CRITICAL-PROD',
},
{
domain: 'vex',
route: '/vex-hub/CVE-2024-21626',
expected: '/security/advisories-vex?q=CVE-2024-21626',
},
{
domain: 'platform',
route: '/scans/scan-42',
expected: '/security/triage?q=scan-42',
},
];
component.onFocus();
component.onQueryChange('test');
for (const testCase of cases) {
const card = createCard(testCase.domain, testCase.route);
component.onCardAction(card, card.actions[0]);
expect(router.navigateByUrl).toHaveBeenCalledWith(testCase.expected);
}
});
it('uses deterministic keyboard escape behavior for focus and close flow', () => {
component.onFocus();
component.onQueryChange('CVE-2024-21626');
component.onKeydown(new KeyboardEvent('keydown', { key: 'Escape' }));
expect(component.query()).toBe('');
expect(component.isFocused()).toBeTrue();
component.onKeydown(new KeyboardEvent('keydown', { key: 'Escape' }));
expect(component.isFocused()).toBeFalse();
});
it('emits click analytics when Ctrl+Enter runs selected primary action', () => {
const card = createCard('findings', '/triage/findings/fnd-ctrl-enter');
component.onFocus();
component.query.set('critical findings');
component.searchResponse.set({
query: 'critical findings',
topK: 10,
cards: [card],
synthesis: null,
diagnostics: {
ftsMatches: 1,
vectorMatches: 0,
entityCardCount: 1,
durationMs: 3,
usedVector: false,
mode: 'fts-only',
},
});
searchClient.recordAnalytics.calls.reset();
router.navigateByUrl.calls.reset();
component.onKeydown(new KeyboardEvent('keydown', { key: 'Enter', ctrlKey: true }));
expect(searchClient.recordAnalytics).toHaveBeenCalledWith([
jasmine.objectContaining({
eventType: 'click',
entityKey: 'findings:sample',
domain: 'findings',
}),
]);
expect(router.navigateByUrl).toHaveBeenCalledWith('/security/findings/fnd-ctrl-enter');
});
it('keeps focus when blur timer races with immediate refocus', async () => {
component.onFocus();
component.onBlur();
component.onFocus();
await new Promise((resolve) => setTimeout(resolve, 250));
expect(component.isFocused()).toBeTrue();
});
function createCard(domain: EntityCard['domain'], route: string): EntityCard {
return {
entityKey: `${domain}:sample`,
entityType: domain === 'policy' ? 'policy_rule' : domain === 'findings' ? 'finding' : domain === 'vex' ? 'vex_statement' : 'docs',
domain,
title: `${domain} sample`,
snippet: `${domain} snippet`,
score: 1,
actions: [
{
label: 'Open',
actionType: 'navigate',
route,
isPrimary: true,
},
],
metadata: {},
sources: [],
};
}
});

View File

@@ -1,6 +1,24 @@
import { normalizeSearchActionRoute } from '../../app/layout/global-search/search-route-matrix';
import {
normalizeSearchActionRoute,
SEARCH_ACTION_ROUTE_MATRIX,
} from '../../app/layout/global-search/search-route-matrix';
describe('normalizeSearchActionRoute', () => {
it('covers at least one route per unified-search domain', () => {
const domains = new Set(SEARCH_ACTION_ROUTE_MATRIX.map((entry) => entry.domain));
expect(domains.has('knowledge')).toBeTrue();
expect(domains.has('findings')).toBeTrue();
expect(domains.has('policy')).toBeTrue();
expect(domains.has('vex')).toBeTrue();
expect(domains.has('platform')).toBeTrue();
});
it('normalizes all matrix routes to expected frontend routes', () => {
for (const entry of SEARCH_ACTION_ROUTE_MATRIX) {
expect(normalizeSearchActionRoute(entry.sourceRoute)).toBe(entry.expectedRoute);
}
});
it('maps findings routes into security finding detail', () => {
expect(normalizeSearchActionRoute('/triage/findings/abc-123')).toBe('/security/findings/abc-123');
});
@@ -18,10 +36,14 @@ describe('normalizeSearchActionRoute', () => {
});
it('maps scan routes into security scans route', () => {
expect(normalizeSearchActionRoute('/scans/scan-42')).toBe('/security/scans/scan-42');
expect(normalizeSearchActionRoute('/scans/scan-42')).toBe('/security/triage?q=scan-42');
});
it('preserves already valid app routes', () => {
expect(normalizeSearchActionRoute('/docs/ops/runbook#overview')).toBe('/docs/ops/runbook#overview');
});
it('falls back to /ops for unknown legacy routes', () => {
expect(normalizeSearchActionRoute('/legacy/does-not-exist')).toBe('/ops');
});
});

View File

@@ -0,0 +1,87 @@
import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { of } from 'rxjs';
import { SearchClient } from '../../app/core/api/search.client';
import { UnifiedSearchClient } from '../../app/core/api/unified-search.client';
describe('UnifiedSearchClient', () => {
let httpMock: HttpTestingController;
let client: UnifiedSearchClient;
beforeEach(() => {
const legacySearch = jasmine.createSpyObj('SearchClient', ['search']);
legacySearch.search.and.returnValue(of({
query: 'legacy',
groups: [],
totalCount: 0,
durationMs: 0,
}));
TestBed.configureTestingModule({
providers: [
UnifiedSearchClient,
{ provide: SearchClient, useValue: legacySearch },
provideHttpClient(),
provideHttpClientTesting(),
],
});
httpMock = TestBed.inject(HttpTestingController);
client = TestBed.inject(UnifiedSearchClient);
});
afterEach(() => {
httpMock.verify();
});
it('sanitizes script/html tags from unified snippets', () => {
let payload: any;
client.search('critical update').subscribe((response) => {
payload = response;
});
const request = httpMock.expectOne('/api/v1/search/query');
expect(request.request.method).toBe('POST');
request.flush({
query: 'critical update',
topK: 10,
cards: [
{
entityKey: 'docs:hardening',
entityType: 'docs',
domain: 'knowledge',
title: 'Hardening Guide',
snippet: "<mark>critical</mark><script>alert('x')</script> update available",
score: 0.99,
actions: [
{
label: 'Open',
actionType: 'navigate',
route: '/docs/modules/advisory-ai/knowledge-search.md',
isPrimary: true,
},
],
metadata: {},
sources: ['knowledge'],
},
],
synthesis: null,
diagnostics: {
ftsMatches: 1,
vectorMatches: 0,
entityCardCount: 1,
durationMs: 7,
usedVector: false,
mode: 'fts-only',
},
});
expect(payload).toBeTruthy();
expect(payload.cards.length).toBe(1);
expect(payload.cards[0].snippet).toBe('critical update available');
});
});

View File

@@ -0,0 +1,116 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router, convertToParamMap, type ParamMap } from '@angular/router';
import { BehaviorSubject } from 'rxjs';
import { PlatformContextStore } from '../../app/core/context/platform-context.store';
import { SearchChatContextService } from '../../app/core/services/search-chat-context.service';
import type { EntityCard } from '../../app/core/api/unified-search.models';
import { SecurityTriageChatHostComponent } from '../../app/features/security/security-triage-chat-host.component';
@Component({
selector: 'app-security-findings-page',
standalone: true,
template: '',
})
class SecurityFindingsPageStubComponent {}
@Component({
selector: 'stellaops-chat',
standalone: true,
template: '',
})
class ChatStubComponent {
@Input() tenantId = 'default';
@Input() initialUserMessage: string | null = null;
@Output() close = new EventEmitter<void>();
@Output() searchForMore = new EventEmitter<string>();
}
describe('SecurityTriageChatHostComponent', () => {
let fixture: ComponentFixture<SecurityTriageChatHostComponent>;
let component: SecurityTriageChatHostComponent;
let queryParamMap$: BehaviorSubject<ParamMap>;
let router: { navigate: jasmine.Spy };
let searchChatContext: SearchChatContextService;
beforeEach(async () => {
queryParamMap$ = new BehaviorSubject<ParamMap>(convertToParamMap({}));
router = {
navigate: jasmine.createSpy('navigate').and.returnValue(Promise.resolve(true)),
};
await TestBed.configureTestingModule({
imports: [SecurityTriageChatHostComponent],
providers: [
SearchChatContextService,
{
provide: ActivatedRoute,
useValue: {
queryParamMap: queryParamMap$.asObservable(),
},
},
{ provide: Router, useValue: router },
{
provide: PlatformContextStore,
useValue: {
tenantId: () => 'test-tenant',
},
},
],
})
.overrideComponent(SecurityTriageChatHostComponent, {
set: {
imports: [SecurityFindingsPageStubComponent, ChatStubComponent],
},
})
.compileComponents();
fixture = TestBed.createComponent(SecurityTriageChatHostComponent);
component = fixture.componentInstance;
searchChatContext = TestBed.inject(SearchChatContextService);
fixture.detectChanges();
});
it('opens assistant from openChat intent with CVE-grounded search context prompt', () => {
const card: EntityCard = {
entityKey: 'cve:CVE-2024-21626',
entityType: 'finding',
domain: 'findings',
title: 'CVE-2024-21626',
snippet: 'Container breakout',
score: 0.99,
severity: 'critical',
actions: [],
metadata: {},
sources: ['findings'],
};
searchChatContext.setSearchToChat({
query: 'CVE-2024-21626',
entityCards: [card],
synthesis: null,
});
queryParamMap$.next(convertToParamMap({
openChat: 'true',
q: 'CVE-2024-21626',
}));
fixture.detectChanges();
expect(component.assistantOpen()).toBeTrue();
expect(component.assistantInitialMessage()).toContain('CVE-2024-21626');
expect(router.navigate).toHaveBeenCalledWith([], jasmine.objectContaining({
queryParams: jasmine.objectContaining({ openChat: null }),
}));
});
it('closes assistant on chat search return signal when query is provided', () => {
component.openAssistantPanel();
expect(component.assistantOpen()).toBeTrue();
component.onChatSearchForMore('CVE-2024-21626');
expect(component.assistantOpen()).toBeFalse();
});
});

View File

@@ -1,4 +1,10 @@
{
"status": "passed",
"failedTests": []
"status": "failed",
"failedTests": [
"1afb01fbb618c47567d7-fd98c577baf3a66db4df",
"1afb01fbb618c47567d7-b551a8493c84ef386f8a",
"5116366078763408f6a4-3b86aa37005d1bc4cde8",
"c1989d0c79e16f9a1fb6-8fc204434b4ce3c66739",
"c1989d0c79e16f9a1fb6-f4d050a00e099a21c774"
]
}

View File

@@ -0,0 +1,224 @@
import { expect, test, type Page } from '@playwright/test';
import {
buildResponse,
emptyResponse,
mockSearchApiDynamic,
setupAuthenticatedSession,
setupBasicMocks,
waitForEntityCards,
waitForResults,
} from './unified-search-fixtures';
const newcomerCard = {
entityKey: 'cve:CVE-2024-21626',
entityType: 'finding',
domain: 'findings',
title: 'CVE-2024-21626 in api-gateway',
snippet: 'Reachable critical vulnerability detected in production workload.',
score: 0.96,
severity: 'critical',
actions: [
{
label: 'Open finding',
actionType: 'navigate',
route: '/triage/findings/fnd-9001',
isPrimary: true,
},
],
sources: ['findings'],
metadata: {},
};
const healthySearchResponse = buildResponse(
'critical findings',
[newcomerCard],
{
summary: 'One critical finding matched. Ask AdvisoryAI for triage guidance.',
template: 'finding_overview',
confidence: 'high',
sourceCount: 1,
domainsCovered: ['findings'],
},
);
const degradedSearchResponse = {
...buildResponse('critical findings', [newcomerCard]),
diagnostics: {
ftsMatches: 1,
vectorMatches: 0,
entityCardCount: 1,
durationMs: 21,
usedVector: false,
mode: 'legacy-fallback',
},
};
const recoveredSearchResponse = {
...buildResponse('CVE-2024-21626', [newcomerCard]),
diagnostics: {
ftsMatches: 4,
vectorMatches: 2,
entityCardCount: 1,
durationMs: 37,
usedVector: true,
mode: 'hybrid',
},
};
test.describe('Assistant entry and search reliability', () => {
test.beforeEach(async ({ page }) => {
await setupBasicMocks(page);
await setupAuthenticatedSession(page);
await mockChatEndpoints(page);
});
test('healthy newcomer flow: search -> Ask AI -> search more -> action route', async ({ page }) => {
await mockSearchApiDynamic(page, {
'critical findings': healthySearchResponse,
'cve-2024-21626': recoveredSearchResponse,
}, recoveredSearchResponse);
await page.goto('/security/triage');
await expect(page.locator('aside.sidebar')).toBeVisible({ timeout: 15_000 });
const searchInput = page.locator('app-global-search input[type="text"]');
await searchInput.focus();
await expect(page.locator('.search__results')).toBeVisible();
const criticalSuggestion = page.locator('.search__suggestions .search__chip', {
hasText: /critical findings/i,
}).first();
await expect(criticalSuggestion).toBeVisible();
const suggestedQuery = (await criticalSuggestion.textContent())?.trim() || 'critical findings';
await searchInput.fill(suggestedQuery);
await expect(searchInput).toHaveValue(/critical findings/i);
await waitForResults(page);
await waitForEntityCards(page, 1);
await page.locator('.entity-card__action--ask-ai').first().click();
const assistantDrawer = page.locator('.assistant-drawer');
await expect(assistantDrawer).toBeVisible();
await expect(assistantDrawer).toBeFocused();
const searchMoreButton = page.locator('.search-more-link');
await expect(searchMoreButton).toBeVisible({ timeout: 10_000 });
await searchMoreButton.click();
await expect(assistantDrawer).toBeHidden();
await page.waitForResponse((response) => {
if (!response.url().includes('/api/v1/search/query')) {
return false;
}
const body = response.request().postData() ?? '';
return body.toLowerCase().includes('cve-2024-21626');
});
await expect(searchInput).toHaveValue(/CVE-2024-21626/i);
await searchInput.focus();
await expect(searchInput).toBeFocused();
// Re-trigger the current query without Enter to avoid auto-select side effects.
await searchInput.fill('CVE-2024-21626 ');
await searchInput.press('Backspace');
await waitForResults(page);
await expect(page.locator('app-entity-card').first()).toBeVisible({ timeout: 10_000 });
await page.locator('.entity-card__action--primary').first().click();
await expect(page).toHaveURL(/\/security\/findings\/fnd-9001/i);
});
test('degraded mode is visible and clears after recovery; focus order remains deterministic', async ({ page }) => {
await mockSearchApiDynamic(page, {
'critical findings': degradedSearchResponse,
'cve-2024-21626': recoveredSearchResponse,
}, recoveredSearchResponse);
await page.goto('/security/triage');
await expect(page.locator('aside.sidebar')).toBeVisible({ timeout: 15_000 });
const searchInput = page.locator('app-global-search input[type="text"]');
await searchInput.focus();
const criticalSuggestion = page.locator('.search__suggestions .search__chip', {
hasText: /critical findings/i,
}).first();
await expect(criticalSuggestion).toBeVisible();
const suggestedQuery = (await criticalSuggestion.textContent())?.trim() || 'critical findings';
await searchInput.fill(suggestedQuery);
await expect(searchInput).toHaveValue(/critical findings/i);
await waitForResults(page);
await waitForEntityCards(page, 1);
const degradedBanner = page.locator('.search__degraded-banner');
await expect(degradedBanner).toBeVisible({ timeout: 10_000 });
await expect(degradedBanner).toContainText(/fallback mode/i);
await searchInput.fill('CVE-2024-21626');
await waitForResults(page);
await waitForEntityCards(page, 1);
await expect(degradedBanner).toBeHidden();
await page.locator('.entity-card__action--ask-ai').first().click();
const assistantDrawer = page.locator('.assistant-drawer');
await expect(assistantDrawer).toBeVisible();
await expect(assistantDrawer).toBeFocused();
await page.keyboard.press('Escape');
await expect(assistantDrawer).toBeHidden();
await expect(page.locator('.assistant-fab')).toBeFocused();
});
});
async function mockChatEndpoints(page: Page): Promise<void> {
await page.route('**/api/v1/advisory-ai/conversations', async (route) => {
if (route.request().method() !== 'POST') {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([]),
});
}
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
conversationId: 'conv-newcomer-1',
tenantId: 'test-tenant',
userId: 'tester',
context: {},
turns: [],
createdAt: '2026-02-25T00:00:00.000Z',
updatedAt: '2026-02-25T00:00:00.000Z',
}),
});
});
await page.route('**/api/v1/advisory-ai/conversations/*/turns', async (route) => {
if (route.request().method() !== 'POST') {
return route.continue();
}
const ssePayload = [
'event: progress',
'data: {"stage":"searching"}',
'',
'event: token',
'data: {"content":"CVE-2024-21626 remains relevant for this finding. "}',
'',
'event: citation',
'data: {"type":"finding","path":"CVE-2024-21626","verified":true}',
'',
'event: done',
'data: {"turnId":"turn-newcomer-1","groundingScore":0.92}',
'',
].join('\n');
return route.fulfill({
status: 200,
headers: {
'content-type': 'text/event-stream; charset=utf-8',
'cache-control': 'no-cache',
},
body: ssePayload,
});
});
}