@a5c-ai/krate 5.0.1-staging.f672fe79b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +29 -0
- package/README.md +183 -0
- package/bin/krate-demo.mjs +23 -0
- package/bin/krate-server.mjs +14 -0
- package/dist/krate-controller-ui.json +2407 -0
- package/dist/krate-lifecycle.json +201 -0
- package/dist/krate-runtime-snapshot.json +2955 -0
- package/dist/krate-summary.json +687 -0
- package/docs/README.md +61 -0
- package/docs/agents/README.md +83 -0
- package/docs/agents/acceptance-test-matrix.md +193 -0
- package/docs/agents/agent-mux-adapter-contract.md +167 -0
- package/docs/agents/agent-mux-source-map.md +310 -0
- package/docs/agents/agent-run-memory-import-spec.md +256 -0
- package/docs/agents/agent-stack-management-spec.md +421 -0
- package/docs/agents/api-contract-spec.md +309 -0
- package/docs/agents/artifacts-writeback-spec.md +145 -0
- package/docs/agents/chart-packaging-spec.md +128 -0
- package/docs/agents/ci-orchestration-spec.md +140 -0
- package/docs/agents/context-assembly-spec.md +219 -0
- package/docs/agents/controller-reconciliation-spec.md +255 -0
- package/docs/agents/crd-schema-spec.md +315 -0
- package/docs/agents/decision-log-open-questions.md +169 -0
- package/docs/agents/developer-implementation-checklist.md +329 -0
- package/docs/agents/dispatching-design.md +262 -0
- package/docs/agents/glossary.md +66 -0
- package/docs/agents/implementation-blueprint.md +324 -0
- package/docs/agents/implementation-rollout-slices.md +251 -0
- package/docs/agents/memory-context-integration-spec.md +194 -0
- package/docs/agents/memory-ontology-schema-spec.md +253 -0
- package/docs/agents/memory-operations-runbook.md +121 -0
- package/docs/agents/mvp-vertical-slice-spec.md +146 -0
- package/docs/agents/observability-audit-spec.md +265 -0
- package/docs/agents/operator-runbook.md +174 -0
- package/docs/agents/org-memory-api-payload-examples.md +333 -0
- package/docs/agents/org-memory-controller-sequence-spec.md +181 -0
- package/docs/agents/org-memory-e2e-fixture-plan.md +161 -0
- package/docs/agents/org-memory-ui-implementation-map.md +114 -0
- package/docs/agents/org-memory-vertical-slice-spec.md +168 -0
- package/docs/agents/org-resource-model-delta-spec.md +111 -0
- package/docs/agents/org-route-resource-model-spec.md +183 -0
- package/docs/agents/org-scoping-namespace-spec.md +114 -0
- package/docs/agents/rbac-secrets-management-spec.md +406 -0
- package/docs/agents/repository-page-integration-spec.md +255 -0
- package/docs/agents/resource-contract-examples.md +808 -0
- package/docs/agents/resource-relationship-map.md +190 -0
- package/docs/agents/security-threat-model.md +188 -0
- package/docs/agents/shared-memory-company-brain-spec.md +358 -0
- package/docs/agents/storage-migration-spec.md +168 -0
- package/docs/agents/subagent-orchestration-spec.md +152 -0
- package/docs/agents/system-overview.md +88 -0
- package/docs/agents/tools-mcp-skills-spec.md +189 -0
- package/docs/agents/traceability-matrix.md +79 -0
- package/docs/agents/ui-flow-spec.md +211 -0
- package/docs/agents/ui-ux-system-spec.md +426 -0
- package/docs/agents/workspace-lifecycle-spec.md +166 -0
- package/docs/architecture-spec.md +78 -0
- package/docs/components/control-plane.md +78 -0
- package/docs/components/data-plane.md +69 -0
- package/docs/components/hooks-events.md +67 -0
- package/docs/components/identity-rbac-policy.md +73 -0
- package/docs/components/kubevela-oam.md +70 -0
- package/docs/components/operations-publishing.md +81 -0
- package/docs/components/runners-ci.md +66 -0
- package/docs/components/web-ui.md +94 -0
- package/docs/external/README.md +47 -0
- package/docs/external/bidirectional-sync-design.md +134 -0
- package/docs/external/cicd-interface.md +64 -0
- package/docs/external/external-backend-controllers.md +170 -0
- package/docs/external/external-backend-crds.md +234 -0
- package/docs/external/external-backend-ui-spec.md +151 -0
- package/docs/external/external-backend-ux-flows.md +115 -0
- package/docs/external/external-object-mapping.md +125 -0
- package/docs/external/git-forge-interface.md +68 -0
- package/docs/external/github-integration-design.md +151 -0
- package/docs/external/issue-tracking-interface.md +66 -0
- package/docs/external/provider-capability-manifests.md +204 -0
- package/docs/external/provider-catalog.md +139 -0
- package/docs/external/provider-rollout-testing.md +78 -0
- package/docs/external/research-results.md +48 -0
- package/docs/external/security-auth-permissions.md +81 -0
- package/docs/external/sync-state-machines.md +108 -0
- package/docs/external/unified-external-backend-model.md +107 -0
- package/docs/external/user-facing-changes.md +67 -0
- package/docs/gaps.md +161 -0
- package/docs/install.md +94 -0
- package/docs/krate-design.md +334 -0
- package/docs/local-minikube.md +55 -0
- package/docs/ontology/README.md +32 -0
- package/docs/ontology/bounded-contexts.md +29 -0
- package/docs/ontology/events-and-hooks.md +32 -0
- package/docs/ontology/oam-kubevela.md +32 -0
- package/docs/ontology/operations-and-release.md +25 -0
- package/docs/ontology/personas-and-actors.md +32 -0
- package/docs/ontology/policies-and-invariants.md +33 -0
- package/docs/ontology/problem-space.md +30 -0
- package/docs/ontology/resource-contracts.md +40 -0
- package/docs/ontology/resource-taxonomy.md +42 -0
- package/docs/ontology/runners-and-ci.md +29 -0
- package/docs/ontology/solution-space.md +24 -0
- package/docs/ontology/storage-and-data-boundaries.md +29 -0
- package/docs/ontology/validation-matrix.md +24 -0
- package/docs/ontology/web-ui-excellent-flows.md +32 -0
- package/docs/ontology/workflows.md +39 -0
- package/docs/ontology/world.md +35 -0
- package/docs/product-requirements.md +62 -0
- package/docs/roadmap-mvp.md +87 -0
- package/docs/system-requirements.md +90 -0
- package/docs/tests/README.md +53 -0
- package/docs/tests/agent-qa-plan.md +63 -0
- package/docs/tests/browser-ui-tests.md +62 -0
- package/docs/tests/ci-quality-gates.md +48 -0
- package/docs/tests/coverage-model.md +64 -0
- package/docs/tests/e2e-scenario-tests.md +53 -0
- package/docs/tests/fixtures-test-data.md +63 -0
- package/docs/tests/observability-reliability-tests.md +54 -0
- package/docs/tests/product-test-matrix.md +145 -0
- package/docs/tests/qa-adoption-roadmap.md +130 -0
- package/docs/tests/qa-automation-plan.md +101 -0
- package/docs/tests/security-compliance-tests.md +57 -0
- package/docs/tests/test-framework-tools.md +88 -0
- package/docs/tests/test-suite-layout.md +121 -0
- package/docs/tests/unit-integration-tests.md +48 -0
- package/docs/todo-kyverno +714 -0
- package/docs/user-stories.md +78 -0
- package/examples/minikube-demo.yaml +190 -0
- package/examples/oam-application.yaml +23 -0
- package/examples/policy-kyverno-pr-title.yaml +18 -0
- package/package.json +63 -0
- package/scripts/build.mjs +29 -0
- package/scripts/setup-minikube.mjs +65 -0
- package/scripts/smoke.mjs +37 -0
- package/scripts/validate-doc-coverage.mjs +152 -0
- package/scripts/validate-package.mjs +93 -0
- package/scripts/validate-ui.mjs +207 -0
- package/src/agent-approval-controller.js +123 -0
- package/src/agent-context-bundles.js +242 -0
- package/src/agent-dispatch-controller.js +86 -0
- package/src/agent-mux-client.js +280 -0
- package/src/agent-permission-review.js +162 -0
- package/src/agent-stack-controller.js +296 -0
- package/src/agent-trigger-controller.js +108 -0
- package/src/api-controller.js +206 -0
- package/src/argocd-gitops.js +43 -0
- package/src/auth.js +265 -0
- package/src/component-catalog.js +41 -0
- package/src/control-plane.js +136 -0
- package/src/controller-client.js +38 -0
- package/src/controller-ui.js +538 -0
- package/src/data-plane.js +178 -0
- package/src/gitea-backend.js +95 -0
- package/src/handoff.js +98 -0
- package/src/hooks-events.js +63 -0
- package/src/http-server.js +151 -0
- package/src/identity-policy.js +86 -0
- package/src/index.js +30 -0
- package/src/kubernetes-controller.js +812 -0
- package/src/kubernetes-resource-gateway.js +48 -0
- package/src/operations.js +112 -0
- package/src/resource-model.js +203 -0
- package/src/runners-ci.js +48 -0
- package/src/runtime.js +196 -0
- package/src/web-ui.js +40 -0
- package/tests/agent-approval-controller.test.js +173 -0
- package/tests/agent-context-bundles.test.js +278 -0
- package/tests/agent-dispatch-controller.test.js +176 -0
- package/tests/agent-mux-client.test.js +204 -0
- package/tests/agent-permission-review.test.js +209 -0
- package/tests/agent-resources.test.js +212 -0
- package/tests/agent-stack-controller.test.js +221 -0
- package/tests/agent-trigger-controller.test.js +211 -0
- package/tests/deployment.test.js +395 -0
- package/tests/e2e/lifecycle.test.js +117 -0
- package/tests/krate.test.js +727 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import { resourceToYaml } from './resource-model.js';
|
|
2
|
+
import { KRATE_ORG_LABEL, KRATE_ORG_NAMESPACE_LABEL, KRATE_RESOURCES, apiResourceName, createKrateKubernetesReconciler, orgNamespaceName } from './kubernetes-controller.js';
|
|
3
|
+
|
|
4
|
+
const controllerEndpoints = [
|
|
5
|
+
{ method: 'GET', path: '/healthz', purpose: 'container and load-balancer health' },
|
|
6
|
+
{ method: 'GET', path: '/api/controller?org=:org', purpose: 'live Krate org model' },
|
|
7
|
+
{ method: 'GET', path: '/api/orgs/:org/resources', purpose: 'org-scoped Krate resource listing' },
|
|
8
|
+
{ method: 'POST', path: '/api/orgs/:org/resources', purpose: 'org-scoped Krate resource apply flow' },
|
|
9
|
+
{ method: 'GET', path: '/api/orgs/:org/resources/:kind/:name', purpose: 'org-scoped Krate resource detail' },
|
|
10
|
+
{ method: 'DELETE', path: '/api/orgs/:org/resources/:kind/:name', purpose: 'org-scoped Krate resource delete flow' },
|
|
11
|
+
{ method: 'GET', path: '/api/orgs/:org/repositories', purpose: 'list org repositories through Krate' },
|
|
12
|
+
{ method: 'POST', path: '/api/orgs/:org/repositories', purpose: 'create org repositories through Krate' },
|
|
13
|
+
{ method: 'GET', path: '/api/orgs/:org/repositories/:name', purpose: 'get org repository details through Krate' },
|
|
14
|
+
{ method: 'DELETE', path: '/api/orgs/:org/repositories/:name', purpose: 'delete org repositories through Krate' },
|
|
15
|
+
{ method: 'GET', path: '/api/orgs/:org/policies', purpose: 'list Krate policy profiles, templates, bindings, Kyverno health, and exception requests' },
|
|
16
|
+
{ method: 'POST', path: '/api/orgs/:org/policies', purpose: 'create org policy bindings in audit or enforce mode' },
|
|
17
|
+
{ method: 'GET', path: '/api/orgs/:org/policy-reports', purpose: 'list normalized Kyverno policy report results' },
|
|
18
|
+
{ method: 'GET', path: '/api/orgs/:org/policy-exception-requests', purpose: 'list pending and approved Krate policy exception requests' },
|
|
19
|
+
{ method: 'POST', path: '/api/orgs/:org/policy-exception-requests', purpose: 'request a temporary policy exception through Krate' },
|
|
20
|
+
{ method: 'GET', path: '/api/watch/orgs/:org/*', purpose: 'org-scoped Krate live event stream bridged to browser updates' },
|
|
21
|
+
{ method: 'POST', path: '/api/git-proxy', purpose: 'repository streaming proxy when configured' },
|
|
22
|
+
{ method: 'GET', path: '/api/orgs/:org/agents/stacks', purpose: 'list agent stacks and capability status' },
|
|
23
|
+
{ method: 'GET', path: '/api/orgs/:org/agents/runs', purpose: 'list agent dispatch runs with queue and status' },
|
|
24
|
+
{ method: 'GET', path: '/api/orgs/:org/agents/rules', purpose: 'list trigger rules and delivery status' },
|
|
25
|
+
{ method: 'GET', path: '/api/orgs/:org/agents/sessions', purpose: 'list agent sessions with lifecycle state' },
|
|
26
|
+
{ method: 'GET', path: '/api/orgs/:org/agents/workspaces', purpose: 'list agent workspaces with lifecycle state' },
|
|
27
|
+
{ method: 'GET', path: '/api/orgs/:org/agents/approvals', purpose: 'list pending and resolved agent approvals' },
|
|
28
|
+
{ method: 'GET', path: '/api/orgs/:org/agents/permissions/review', purpose: 'explainable permission check for agent dispatch' },
|
|
29
|
+
{ method: 'GET', path: '/api/orgs/:org/agents/adapters', purpose: 'list agent adapters and transport bindings' },
|
|
30
|
+
{ method: 'GET', path: '/api/orgs/:org/agents/providers', purpose: 'list model provider configurations' },
|
|
31
|
+
{ method: 'GET', path: '/api/orgs/:org/agents/projects', purpose: 'list agent projects with board config' },
|
|
32
|
+
{ method: 'POST', path: '/api/orgs/:org/agents/dispatch', purpose: 'create manual agent dispatch run' },
|
|
33
|
+
{ method: 'POST', path: '/api/orgs/:org/agents/approvals/:name/decide', purpose: 'approve or deny a pending agent approval request' },
|
|
34
|
+
{ method: 'POST', path: '/api/orgs/:org/agents/triggers/process', purpose: 'evaluate an event against trigger rules and dispatch matching agents' }
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const runtimeComponents = [
|
|
38
|
+
{ id: 'identity-access', title: 'Identity and access', area: 'identity', resources: ['User', 'Team', 'Invite', 'IdentityMapping', 'AuthProvider'], docs: 'src/auth.js' },
|
|
39
|
+
{ id: 'api-controller', title: 'Krate API controller', area: 'api', resources: ['Repository', 'PullRequest', 'Pipeline'], docs: 'src/api-controller.js' },
|
|
40
|
+
{ id: 'krate-resource-client', title: 'Krate resource client', area: 'control-plane', resources: ['Repository', 'BranchProtection', 'RefPolicy'], docs: 'src/kubernetes-controller.js' },
|
|
41
|
+
{ id: 'repository-service', title: 'Repository service', area: 'data-plane', resources: ['Repository', 'BranchProtection', 'RefPolicy'], docs: 'src/data-plane.js' },
|
|
42
|
+
{ id: 'runners-ci', title: 'Runner scheduler', area: 'ci', resources: ['RunnerPool', 'Pipeline', 'Job'], docs: 'src/kubernetes-controller.js' },
|
|
43
|
+
{ id: 'hooks-events', title: 'Webhook bus', area: 'events', resources: ['WebhookSubscription', 'WebhookDelivery'], docs: 'src/kubernetes-controller.js' },
|
|
44
|
+
{ id: 'policy-engine', title: 'Kyverno policy engine', area: 'policy', resources: ['PolicyProfile', 'PolicyTemplate', 'PolicyBinding', 'PolicyExceptionRequest'], docs: 'docs/todo-kyverno' },
|
|
45
|
+
{ id: 'agent-orchestration', title: 'Agent orchestration', area: 'agents', resources: ['AgentStack', 'AgentDispatchRun', 'AgentTriggerRule', 'AgentSession', 'AgentWorkspace', 'AgentApproval', 'AgentAdapter', 'AgentProviderConfig', 'AgentProject'], docs: 'docs/agents/' }
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
export function createControllerUiModel(source, options = {}) {
|
|
49
|
+
const snapshot = normalizeSnapshot(source);
|
|
50
|
+
const organizations = ensureOrganizations(snapshot.resources.Organization || [], snapshot.namespace);
|
|
51
|
+
const requestedOrg = options.organization || options.org || process.env.KRATE_ORG || '';
|
|
52
|
+
const activeOrg = organizations.find((org) => org.slug === requestedOrg || org.name === requestedOrg) || organizations[0];
|
|
53
|
+
const resources = KRATE_RESOURCES.map((definition) => {
|
|
54
|
+
const rawItems = snapshot.resources[definition.kind] || [];
|
|
55
|
+
const items = filterResourceItemsForOrg(definition, rawItems, activeOrg?.slug);
|
|
56
|
+
return {
|
|
57
|
+
kind: definition.kind,
|
|
58
|
+
plural: definition.plural,
|
|
59
|
+
apiResource: apiResourceName(definition),
|
|
60
|
+
count: items.length,
|
|
61
|
+
names: items.map((item) => item.metadata?.name).filter(Boolean),
|
|
62
|
+
items,
|
|
63
|
+
phases: summarizePhases(items),
|
|
64
|
+
storage: definition.storage,
|
|
65
|
+
yaml: items[0] ? resourceToYaml(items[0]) : null,
|
|
66
|
+
action: snapshot.commands.find((command) => command.kind === definition.kind) || {
|
|
67
|
+
list: `Open ${definition.kind} records in ${activeOrg?.namespace || snapshot.namespace}`,
|
|
68
|
+
watch: `Watch ${definition.kind} updates in ${activeOrg?.namespace || snapshot.namespace}`,
|
|
69
|
+
apply: 'Save resource changes',
|
|
70
|
+
delete: `Delete ${definition.kind} in ${activeOrg?.namespace || snapshot.namespace}`
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
const users = filterByOrg(snapshot.resources.User || [], activeOrg?.slug);
|
|
75
|
+
const teams = filterByOrg(snapshot.resources.Team || [], activeOrg?.slug);
|
|
76
|
+
const invites = filterByOrg(snapshot.resources.Invite || [], activeOrg?.slug);
|
|
77
|
+
const identityMappings = filterByOrg(snapshot.resources.IdentityMapping || [], activeOrg?.slug);
|
|
78
|
+
const authProviders = filterByOrg(snapshot.resources.AuthProvider || [], activeOrg?.slug);
|
|
79
|
+
const repositoryPermissions = filterByOrg(snapshot.resources.RepositoryPermission || [], activeOrg?.slug);
|
|
80
|
+
const sshKeys = filterByOrg(snapshot.resources.SSHKey || [], activeOrg?.slug);
|
|
81
|
+
const identityReconciliation = createKrateKubernetesReconciler({ namespace: snapshot.namespace }).reconcileIdentityAccessResources({ User: users, Team: teams, Invite: invites, IdentityMapping: identityMappings, RepositoryPermission: repositoryPermissions, SSHKey: sshKeys });
|
|
82
|
+
const identityView = createIdentityView({ users, teams, invites, identityMappings, authProviders, permissions: repositoryPermissions, sshKeys, reconciliation: identityReconciliation });
|
|
83
|
+
identityView.org = activeOrg?.slug;
|
|
84
|
+
const repositories = filterByOrg(snapshot.resources.Repository || [], activeOrg?.slug);
|
|
85
|
+
const pullRequests = filterByOrg(snapshot.resources.PullRequest || [], activeOrg?.slug);
|
|
86
|
+
const pipelines = filterByOrg(snapshot.resources.Pipeline || [], activeOrg?.slug);
|
|
87
|
+
const jobs = filterByOrg(snapshot.resources.Job || [], activeOrg?.slug);
|
|
88
|
+
const runnerPools = filterByOrg(snapshot.resources.RunnerPool || [], activeOrg?.slug);
|
|
89
|
+
const webhookSubscriptions = filterByOrg(snapshot.resources.WebhookSubscription || [], activeOrg?.slug);
|
|
90
|
+
const webhookDeliveries = filterByOrg(snapshot.resources.WebhookDelivery || [], activeOrg?.slug);
|
|
91
|
+
const policyProfiles = filterByOrg(snapshot.resources.PolicyProfile || [], activeOrg?.slug);
|
|
92
|
+
const policyTemplates = filterByOrg(snapshot.resources.PolicyTemplate || [], activeOrg?.slug).concat((snapshot.resources.PolicyTemplate || []).filter((item) => !item.spec?.organizationRef));
|
|
93
|
+
const policyBindings = filterByOrg(snapshot.resources.PolicyBinding || [], activeOrg?.slug);
|
|
94
|
+
const policyExceptionRequests = filterByOrg(snapshot.resources.PolicyExceptionRequest || [], activeOrg?.slug);
|
|
95
|
+
const policyEngine = createPolicyEngineView({ kyverno: snapshot.kyverno, policyProfiles, policyTemplates, policyBindings, policyExceptionRequests, org: activeOrg?.slug, namespace: activeOrg?.namespace || snapshot.namespace });
|
|
96
|
+
const agentStacks = filterByOrg(snapshot.resources.AgentStack || [], activeOrg?.slug);
|
|
97
|
+
const agentDispatchRuns = filterByOrg(snapshot.resources.AgentDispatchRun || [], activeOrg?.slug);
|
|
98
|
+
const agentTriggerRules = filterByOrg(snapshot.resources.AgentTriggerRule || [], activeOrg?.slug);
|
|
99
|
+
const agentSessions = filterByOrg(snapshot.resources.AgentSession || [], activeOrg?.slug);
|
|
100
|
+
const agentWorkspaces = filterByOrg(snapshot.resources.AgentWorkspace || [], activeOrg?.slug);
|
|
101
|
+
const agentApprovals = filterByOrg(snapshot.resources.AgentApproval || [], activeOrg?.slug);
|
|
102
|
+
const agentAdapters = filterByOrg(snapshot.resources.AgentAdapter || [], activeOrg?.slug);
|
|
103
|
+
const agentProviders = filterByOrg(snapshot.resources.AgentProviderConfig || [], activeOrg?.slug);
|
|
104
|
+
const agentProjects = filterByOrg(snapshot.resources.AgentProject || [], activeOrg?.slug);
|
|
105
|
+
const agentGateway = filterByOrg(snapshot.resources.AgentGatewayConfig || [], activeOrg?.slug);
|
|
106
|
+
const agentTranscripts = filterByOrg(snapshot.resources.AgentSessionTranscript || [], activeOrg?.slug);
|
|
107
|
+
|
|
108
|
+
const agentView = {
|
|
109
|
+
org: activeOrg?.slug,
|
|
110
|
+
stacks: { count: agentStacks.length, items: agentStacks },
|
|
111
|
+
runs: { count: agentDispatchRuns.length, items: agentDispatchRuns, active: agentDispatchRuns.filter(r => r.status?.phase && r.status.phase !== 'Completed' && r.status.phase !== 'Failed') },
|
|
112
|
+
rules: { count: agentTriggerRules.length, items: agentTriggerRules },
|
|
113
|
+
sessions: { count: agentSessions.length, items: agentSessions },
|
|
114
|
+
workspaces: { count: agentWorkspaces.length, items: agentWorkspaces },
|
|
115
|
+
approvals: { count: agentApprovals.length, items: agentApprovals, pending: agentApprovals.filter(a => !a.status?.phase || a.status.phase === 'Pending') },
|
|
116
|
+
adapters: { count: agentAdapters.length, items: agentAdapters },
|
|
117
|
+
providers: { count: agentProviders.length, items: agentProviders },
|
|
118
|
+
projects: { count: agentProjects.length, items: agentProjects },
|
|
119
|
+
gateway: agentGateway[0] || null,
|
|
120
|
+
transcripts: { count: agentTranscripts.length, items: agentTranscripts },
|
|
121
|
+
};
|
|
122
|
+
const deploymentApplications = filterByOrg(snapshot.resources.KubeVelaApplication || [], activeOrg?.slug);
|
|
123
|
+
const deploymentReleases = filterByOrg(snapshot.resources.KubeVelaApplicationRevision || [], activeOrg?.slug);
|
|
124
|
+
const deploymentComponents = snapshot.resources.KubeVelaComponentDefinition || [];
|
|
125
|
+
const deploymentWorkloads = snapshot.resources.KubeVelaWorkloadDefinition || [];
|
|
126
|
+
const deploymentTraits = snapshot.resources.KubeVelaTraitDefinition || [];
|
|
127
|
+
const deploymentScopes = snapshot.resources.KubeVelaScopeDefinition || [];
|
|
128
|
+
const deploymentPolicyDefinitions = snapshot.resources.KubeVelaPolicyDefinition || [];
|
|
129
|
+
const deploymentPolicies = filterByOrg(snapshot.resources.KubeVelaPolicy || [], activeOrg?.slug);
|
|
130
|
+
const deploymentAutomationSteps = snapshot.resources.KubeVelaWorkflowStepDefinition || [];
|
|
131
|
+
const deploymentAutomations = filterByOrg(snapshot.resources.KubeVelaWorkflow || [], activeOrg?.slug);
|
|
132
|
+
const deploymentManagedResources = filterByOrg(snapshot.resources.KubeVelaResourceTracker || [], activeOrg?.slug);
|
|
133
|
+
const events = snapshot.events || [];
|
|
134
|
+
const workspaceConnected = Boolean(snapshot.kubectl?.available);
|
|
135
|
+
const apiInstalled = resources.some((resource) => resource.count > 0) || Boolean(snapshot.apiService) || snapshot.crds?.length > 0;
|
|
136
|
+
const validation = [
|
|
137
|
+
{ name: 'Krate workspace is connected', passed: workspaceConnected, evidence: snapshot.kubectl?.context || snapshot.kubectl?.errors?.[0] || 'Krate workspace unavailable' },
|
|
138
|
+
{ name: 'Krate API surface is discoverable', passed: apiInstalled, evidence: snapshot.apiService?.metadata?.name || `${snapshot.crds?.length || 0} Krate resources discovered` },
|
|
139
|
+
{ name: 'Repository management uses org-scoped Krate actions', passed: true, evidence: '/api/orgs/:org/repositories and /api/orgs/:org/resources call src/kubernetes-controller.js' },
|
|
140
|
+
{ name: 'Krate API is separated from delivery execution', passed: true, evidence: 'src/api-controller.js delegates resource operations through the Krate gateway' },
|
|
141
|
+
{ name: 'Live streams use Krate watch', passed: true, evidence: '/api/watch/orgs/:org/* uses Krate live updates' },
|
|
142
|
+
{ name: 'UI renders live and empty states without demo data', passed: true, evidence: 'apps/web Server Components consume fetchControllerUiModel() only' }
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
product: 'Krate',
|
|
147
|
+
status: workspaceConnected && apiInstalled ? 'ready' : 'degraded',
|
|
148
|
+
namespace: activeOrg?.namespace || snapshot.namespace,
|
|
149
|
+
platformNamespace: snapshot.namespace,
|
|
150
|
+
org: activeOrg,
|
|
151
|
+
orgs: organizations,
|
|
152
|
+
generatedAt: snapshot.generatedAt || new Date().toISOString(),
|
|
153
|
+
correlationId: snapshot.correlationId,
|
|
154
|
+
controller: {
|
|
155
|
+
mode: 'krate-workspace',
|
|
156
|
+
endpoints: controllerEndpoints,
|
|
157
|
+
architecture: snapshot.architecture || defaultArchitecture(snapshot.namespace),
|
|
158
|
+
storage: snapshot.storage,
|
|
159
|
+
connection: { available: workspaceConnected, context: snapshot.kubectl?.context || null, errors: snapshot.kubectl?.errors || [] },
|
|
160
|
+
apiService: snapshot.apiService ? snapshot.apiService.metadata?.name : null,
|
|
161
|
+
commands: snapshot.commands
|
|
162
|
+
},
|
|
163
|
+
metrics: {
|
|
164
|
+
components: runtimeComponents.length,
|
|
165
|
+
resources: resources.reduce((total, resource) => total + resource.count, 0),
|
|
166
|
+
events: events.length,
|
|
167
|
+
auditEntries: 0,
|
|
168
|
+
users: users.length,
|
|
169
|
+
teams: teams.length,
|
|
170
|
+
invites: invites.length,
|
|
171
|
+
repositories: repositories.length,
|
|
172
|
+
pullRequests: pullRequests.length,
|
|
173
|
+
pipelines: pipelines.length,
|
|
174
|
+
jobs: jobs.length,
|
|
175
|
+
runnerPools: runnerPools.length,
|
|
176
|
+
webhookDeliveries: webhookDeliveries.length,
|
|
177
|
+
policyViolations: policyEngine.violations.length,
|
|
178
|
+
policyBindings: policyBindings.length,
|
|
179
|
+
deployments: deploymentApplications.length,
|
|
180
|
+
releases: deploymentReleases.length,
|
|
181
|
+
agentStacks: agentStacks.length,
|
|
182
|
+
agentRuns: agentDispatchRuns.length,
|
|
183
|
+
agentSessions: agentSessions.length,
|
|
184
|
+
greenChecks: validation.filter((item) => item.passed).length,
|
|
185
|
+
totalChecks: validation.length
|
|
186
|
+
},
|
|
187
|
+
components: runtimeComponents,
|
|
188
|
+
resources,
|
|
189
|
+
events: events.slice(-8).map((event) => ({
|
|
190
|
+
type: event.type || event.reason || 'KrateEvent',
|
|
191
|
+
storage: 'kubernetes',
|
|
192
|
+
resource: event.involvedObject ? `${event.involvedObject.kind}/${event.involvedObject.namespace || snapshot.namespace}/${event.involvedObject.name}` : event.metadata?.name,
|
|
193
|
+
actor: event.reportingController || event.source?.component || 'kubernetes',
|
|
194
|
+
allowed: true,
|
|
195
|
+
message: event.message || event.note || ''
|
|
196
|
+
})),
|
|
197
|
+
auditLog: [],
|
|
198
|
+
delivery: createDeliveryView({ applications: deploymentApplications, releases: deploymentReleases, components: deploymentComponents, workloads: deploymentWorkloads, traits: deploymentTraits, scopes: deploymentScopes, policyDefinitions: deploymentPolicyDefinitions, policies: deploymentPolicies, automationSteps: deploymentAutomationSteps, automations: deploymentAutomations, managedResources: deploymentManagedResources }),
|
|
199
|
+
policyEngine,
|
|
200
|
+
agents: agentView,
|
|
201
|
+
identity: identityView,
|
|
202
|
+
validation,
|
|
203
|
+
permissions: snapshot.permissions || [],
|
|
204
|
+
views: {
|
|
205
|
+
dashboard: {
|
|
206
|
+
repositories,
|
|
207
|
+
excellentFlows: ['Create or import a repository', 'Browse code and copy clone commands', 'Review and merge a pull request', 'Debug a failing pipeline run', 'Edit runner pool capacity', 'Inspect and replay webhook deliveries', 'Save a triage View'],
|
|
208
|
+
cards: dashboardCards({ repositories, pullRequests, pipelines, runnerPools, webhookDeliveries })
|
|
209
|
+
},
|
|
210
|
+
pullRequestReview: pullRequests[0] ? pullRequestReview(pullRequests[0], pipelines, jobs) : null,
|
|
211
|
+
failingRun: pipelines.find((pipeline) => pipeline.status?.phase === 'Failed') ? failingRun(pipelines.find((pipeline) => pipeline.status?.phase === 'Failed'), jobs) : null,
|
|
212
|
+
runnerPoolEditor: runnerPools[0] ? runnerPoolEditor(runnerPools[0]) : null,
|
|
213
|
+
webhookInspector: webhookSubscriptions[0] ? webhookInspector(webhookSubscriptions[0], webhookDeliveries) : null,
|
|
214
|
+
triageView: (snapshot.resources.View || [])[0] ? triageView((snapshot.resources.View || [])[0]) : null,
|
|
215
|
+
identityAdmin: identityView,
|
|
216
|
+
policyCenter: policyEngine
|
|
217
|
+
},
|
|
218
|
+
operations: {
|
|
219
|
+
image: 'ghcr.io/<owner>/<repo>/krate-controller',
|
|
220
|
+
chart: 'charts/krate',
|
|
221
|
+
installCommands: ['Install Krate release package', 'Apply the demo workspace configuration'],
|
|
222
|
+
releaseGates: ['npm run check', 'docker build', 'helm package charts/krate', 'npm pack --json']
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
function ensureOrganizations(organizations = [], platformNamespace = 'krate-system') {
|
|
229
|
+
if (organizations.length) return organizations.map((org) => {
|
|
230
|
+
const slug = org.spec?.slug || org.metadata?.name;
|
|
231
|
+
const namespace = org.spec?.namespaceName || org.metadata?.labels?.[KRATE_ORG_NAMESPACE_LABEL] || orgNamespaceName(slug);
|
|
232
|
+
return { name: org.metadata?.name, slug, displayName: org.spec?.displayName || slug, namespace, platformNamespace: org.metadata?.namespace || platformNamespace };
|
|
233
|
+
});
|
|
234
|
+
const slug = 'default';
|
|
235
|
+
return [{ name: slug, slug, displayName: 'Default org', namespace: orgNamespaceName(slug), platformNamespace }];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
function filterResourceItemsForOrg(definition, items = [], org) {
|
|
240
|
+
if (!org) return [];
|
|
241
|
+
if (definition.kind === 'Organization') return items.filter((item) => (item.spec?.slug || item.metadata?.name) === org);
|
|
242
|
+
if (definition.kind === 'OrgNamespaceBinding') return filterByOrg(items, org);
|
|
243
|
+
if (definition.namespace && definition.namespace !== orgNamespaceName(org)) return items;
|
|
244
|
+
return filterByOrg(items, org);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function filterByOrg(items = [], org) {
|
|
248
|
+
if (!org) return items;
|
|
249
|
+
return items.filter((item) => {
|
|
250
|
+
const itemOrg = item.spec?.organizationRef || item.metadata?.labels?.[KRATE_ORG_LABEL];
|
|
251
|
+
return itemOrg === org;
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function normalizeSnapshot(source = {}) {
|
|
256
|
+
const raw = typeof source.snapshot === 'function' ? source.snapshot() : source;
|
|
257
|
+
if (raw?.source === 'kubernetes') return { commands: [], crds: [], events: [], permissions: [], storage: {}, ...raw, resources: raw.resources || {} };
|
|
258
|
+
const resources = raw?.resources || {};
|
|
259
|
+
return {
|
|
260
|
+
source: 'runtime-snapshot',
|
|
261
|
+
namespace: raw?.namespace || 'krate-system',
|
|
262
|
+
generatedAt: new Date().toISOString(),
|
|
263
|
+
correlationId: null,
|
|
264
|
+
kubectl: { available: false, context: null, errors: ['runtime snapshot supplied outside the Krate workspace path'] },
|
|
265
|
+
apiService: null,
|
|
266
|
+
crds: [],
|
|
267
|
+
resources,
|
|
268
|
+
events: raw?.events || [],
|
|
269
|
+
permissions: [],
|
|
270
|
+
architecture: raw?.architecture || defaultArchitecture(raw?.namespace || 'krate-system'),
|
|
271
|
+
storage: raw?.storage || {},
|
|
272
|
+
commands: []
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function summarizePhases(items) {
|
|
277
|
+
return items.reduce((summary, item) => {
|
|
278
|
+
const phase = item.status?.phase || (item.status?.ready === true ? 'Ready' : item.status?.conditions?.[0]?.type) || 'Unspecified';
|
|
279
|
+
summary[phase] = (summary[phase] || 0) + 1;
|
|
280
|
+
return summary;
|
|
281
|
+
}, {});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function dashboardCards({ repositories, pullRequests, pipelines, runnerPools, webhookDeliveries }) {
|
|
285
|
+
return [
|
|
286
|
+
{ label: 'Repositories', value: repositories.length, href: '/repositories' },
|
|
287
|
+
{ label: 'Pull requests', value: pullRequests.length, href: '/inbox' },
|
|
288
|
+
{ label: 'Runs', value: pipelines.length, href: '/runs' },
|
|
289
|
+
{ label: 'Runner pools', value: runnerPools.length, href: '/runners-ci' },
|
|
290
|
+
{ label: 'Webhook deliveries', value: webhookDeliveries.length, href: '/hooks-events' }
|
|
291
|
+
];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function defaultArchitecture(namespace) {
|
|
295
|
+
return {
|
|
296
|
+
apiController: { role: 'krate-api-controller', scope: 'HTTP route orchestration, request validation, API errors, and workflow affordances; never owns delivery reconciliation loops', owns: ['/api/controller', '/api/orgs/:org/resources', '/api/orgs/:org/repositories', '/api/watch/orgs/:org/*'], delegatesTo: ['krate-resource-gateway', 'repository-service'] },
|
|
297
|
+
resourceGateway: { role: 'krate-resource-gateway', scope: 'Narrow application port translating API controller intent into Krate resource-client calls', namespace, delegatesTo: ['krate-resource-client'] },
|
|
298
|
+
resourceClient: { role: 'krate-resource-client', scope: 'Krate resource operations and live streams; no UI flow ownership', namespace, owns: ['Krate resources', 'aggregated API resources'] },
|
|
299
|
+
deliveryReconciler: { role: 'krate-delivery-reconciler', scope: 'Repository status projection, repository hosting intent, policy projection, and data-plane sync intent; never owns HTTP routes or browser flows', namespace, delegatesTo: ['krate-resource-gateway', 'repository-service'] },
|
|
300
|
+
repositoryService: { role: 'repository-service', scope: 'repository streaming and SSH hosting, object storage, and search indexing', boundary: process.env.KRATE_GITEA_HTTP_URL || 'repository service not configured' }
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function pullRequestReview(pullRequest, pipelines, jobs) {
|
|
305
|
+
const pipelineRuns = pipelines.filter((pipeline) => pipeline.spec?.pullRequest === pullRequest.metadata?.name || pipeline.metadata?.labels?.pullrequest === pullRequest.metadata?.name);
|
|
306
|
+
return {
|
|
307
|
+
pullRequest,
|
|
308
|
+
changedFiles: pullRequest.status?.changedFiles || [],
|
|
309
|
+
pipelineRuns,
|
|
310
|
+
jobs: jobs.filter((job) => pipelineRuns.some((pipeline) => job.spec?.pipeline === pipeline.metadata?.name)),
|
|
311
|
+
yaml: resourceToYaml(pullRequest),
|
|
312
|
+
keyboardShortcuts: ['j/k navigate files', 'a approve', 'm merge when allowed']
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function failingRun(pipeline, jobs) {
|
|
317
|
+
return {
|
|
318
|
+
pipeline,
|
|
319
|
+
jobs: jobs.filter((job) => job.spec?.pipeline === pipeline.metadata?.name),
|
|
320
|
+
stream: `/api/watch/orgs/${pipeline.spec?.organizationRef || pipeline.metadata?.labels?.['krate.a5c.ai/org'] || 'default'}/pipelines/${pipeline.metadata?.name}`,
|
|
321
|
+
actions: ['rerun from step', 'open run events', 'create incident', 'copy diagnostic summary']
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function runnerPoolEditor(resource) {
|
|
326
|
+
return {
|
|
327
|
+
resource,
|
|
328
|
+
fields: ['image', 'resources', 'warmReplicas', 'maxReplicas', 'trustTier', 'cache'],
|
|
329
|
+
saveModes: ['save capacity', 'open review'],
|
|
330
|
+
yaml: resourceToYaml(resource)
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function webhookInspector(subscription, deliveries) {
|
|
335
|
+
return {
|
|
336
|
+
subscription,
|
|
337
|
+
deliveries: deliveries.filter((delivery) => delivery.spec?.subscription === subscription.metadata?.name || delivery.metadata?.labels?.subscription === subscription.metadata?.name),
|
|
338
|
+
columns: ['phase', 'signature', 'attempts', 'response', 'latency'],
|
|
339
|
+
actions: ['replay', 'disable subscription', 'copy curl']
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function triageView(view) {
|
|
344
|
+
return { resource: view, yaml: resourceToYaml(view), selector: view.spec?.selector || {} };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
function createIdentityView({ users, teams, invites, identityMappings, authProviders, permissions, sshKeys, reconciliation }) {
|
|
350
|
+
const reconciliationStatuses = reconciliation?.desiredStatuses || [];
|
|
351
|
+
const reconciliationPhases = reconciliationStatuses.reduce((summary, item) => ({ ...summary, [item.phase]: (summary[item.phase] || 0) + 1 }), {});
|
|
352
|
+
return {
|
|
353
|
+
counts: {
|
|
354
|
+
users: users.length,
|
|
355
|
+
teams: teams.length,
|
|
356
|
+
pendingInvites: invites.filter((invite) => (invite.status?.phase || 'Pending') === 'Pending').length,
|
|
357
|
+
mappings: identityMappings.length,
|
|
358
|
+
repositoryGrants: permissions.length,
|
|
359
|
+
sshKeys: sshKeys.length
|
|
360
|
+
},
|
|
361
|
+
providers: authProviders.map((provider) => ({
|
|
362
|
+
name: provider.metadata?.name,
|
|
363
|
+
label: provider.spec?.label || provider.metadata?.name,
|
|
364
|
+
type: provider.spec?.type || 'oidc',
|
|
365
|
+
enabled: provider.spec?.enabled !== false,
|
|
366
|
+
phase: provider.status?.phase || 'Unknown'
|
|
367
|
+
})),
|
|
368
|
+
users: users.map((user) => ({
|
|
369
|
+
name: user.metadata?.name,
|
|
370
|
+
displayName: user.spec?.displayName || user.metadata?.name,
|
|
371
|
+
email: user.spec?.email || '',
|
|
372
|
+
teams: user.spec?.teams || [],
|
|
373
|
+
admin: Boolean(user.spec?.admin),
|
|
374
|
+
disabled: Boolean(user.spec?.disabled),
|
|
375
|
+
phase: user.status?.phase || 'Unknown'
|
|
376
|
+
})),
|
|
377
|
+
teams: teams.map((team) => ({
|
|
378
|
+
name: team.metadata?.name,
|
|
379
|
+
displayName: team.spec?.displayName || team.metadata?.name,
|
|
380
|
+
members: team.spec?.members || [],
|
|
381
|
+
maintainers: team.spec?.maintainers || [],
|
|
382
|
+
repositoryGrants: team.spec?.repositoryGrants || []
|
|
383
|
+
})),
|
|
384
|
+
invites: invites.map((invite) => ({
|
|
385
|
+
name: invite.metadata?.name,
|
|
386
|
+
email: invite.spec?.email,
|
|
387
|
+
role: invite.spec?.role || 'member',
|
|
388
|
+
teams: invite.spec?.teams || [],
|
|
389
|
+
phase: invite.status?.phase || 'Pending',
|
|
390
|
+
expiresAt: invite.spec?.expiresAt || ''
|
|
391
|
+
})),
|
|
392
|
+
mappings: identityMappings.map((mapping) => ({
|
|
393
|
+
name: mapping.metadata?.name,
|
|
394
|
+
user: mapping.spec?.user,
|
|
395
|
+
provider: mapping.spec?.provider,
|
|
396
|
+
workspaceIdentity: mapping.spec?.workspaceIdentity?.name || mapping.spec?.subject,
|
|
397
|
+
repositoryIdentity: mapping.spec?.repositoryIdentity?.username || mapping.spec?.user,
|
|
398
|
+
phase: mapping.status?.phase || 'Unknown'
|
|
399
|
+
})),
|
|
400
|
+
permissions: permissions.map((permission) => ({
|
|
401
|
+
name: permission.metadata?.name,
|
|
402
|
+
repository: permission.spec?.repository,
|
|
403
|
+
subject: permission.spec?.subject,
|
|
404
|
+
subjectKind: permission.spec?.subjectKind || 'user',
|
|
405
|
+
permission: permission.spec?.permission || 'read',
|
|
406
|
+
revoked: Boolean(permission.spec?.revoked || permission.status?.phase === 'Revoked'),
|
|
407
|
+
phase: permission.status?.phase || 'Unknown'
|
|
408
|
+
})),
|
|
409
|
+
sshKeys: sshKeys.map((key) => ({
|
|
410
|
+
name: key.metadata?.name,
|
|
411
|
+
title: key.spec?.title || key.metadata?.name,
|
|
412
|
+
owner: key.spec?.owner || key.spec?.user || '',
|
|
413
|
+
scope: key.spec?.scope || 'user',
|
|
414
|
+
repository: key.spec?.repository || '',
|
|
415
|
+
revoked: Boolean(key.spec?.revoked || key.status?.phase === 'Revoked'),
|
|
416
|
+
phase: key.status?.phase || 'Unknown'
|
|
417
|
+
})),
|
|
418
|
+
reconciliation: {
|
|
419
|
+
counts: reconciliation?.counts || {},
|
|
420
|
+
phases: reconciliationPhases,
|
|
421
|
+
statuses: reconciliationStatuses.map((item) => ({
|
|
422
|
+
kind: item.kind,
|
|
423
|
+
name: item.name,
|
|
424
|
+
phase: item.phase,
|
|
425
|
+
checks: (item.conditions || []).map((condition) => readableCondition(condition))
|
|
426
|
+
})),
|
|
427
|
+
nextActions: (reconciliation?.syncIntents || []).map((intent) => readableIntent(intent))
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
function readableCondition(condition = {}) {
|
|
434
|
+
const subject = String(condition.type || 'AccessCheck').replace(/([a-z])([A-Z])/g, '$1 $2');
|
|
435
|
+
return `${subject}: ${condition.status === 'False' ? 'needs attention' : 'ready'}`;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function readableIntent(intent = {}) {
|
|
439
|
+
const action = String(intent.action || 'sync-access').replace(/-/g, ' ');
|
|
440
|
+
const subject = intent.user || intent.team || intent.subject || intent.owner || intent.name || intent.repository || 'workspace access';
|
|
441
|
+
return `${action}: ${subject}`;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function createPolicyEngineView({ kyverno = {}, policyProfiles = [], policyTemplates = [], policyBindings = [], policyExceptionRequests = [], org = 'default', namespace = 'krate-system' }) {
|
|
445
|
+
const reports = kyverno.reports || { results: [], violations: [] };
|
|
446
|
+
const violations = reports.violations || [];
|
|
447
|
+
const detected = Boolean(kyverno.detected);
|
|
448
|
+
const mode = kyverno.mode || 'disabled';
|
|
449
|
+
const health = mode === 'disabled' ? 'disabled' : detected && !(kyverno.degraded || []).length ? 'ready' : 'degraded';
|
|
450
|
+
return {
|
|
451
|
+
engine: 'kyverno',
|
|
452
|
+
mode,
|
|
453
|
+
health,
|
|
454
|
+
detected,
|
|
455
|
+
namespace: kyverno.namespace || 'kyverno',
|
|
456
|
+
policyNamespace: kyverno.policyNamespace || namespace,
|
|
457
|
+
requireForEnforceMode: kyverno.requireForEnforceMode !== false,
|
|
458
|
+
org,
|
|
459
|
+
profiles: policyProfiles.map(policySummary),
|
|
460
|
+
templates: policyTemplates.map(policySummary),
|
|
461
|
+
bindings: policyBindings.map(policySummary),
|
|
462
|
+
exceptionRequests: policyExceptionRequests.map(policySummary),
|
|
463
|
+
kyvernoResources: Object.fromEntries(Object.entries(kyverno.resources || {}).map(([kind, items]) => [kind, Array.isArray(items) ? items.length : 0])),
|
|
464
|
+
controllers: kyverno.controllers || [],
|
|
465
|
+
permissions: kyverno.permissions || [],
|
|
466
|
+
reports: {
|
|
467
|
+
policyReports: reports.policyReports?.length || 0,
|
|
468
|
+
clusterPolicyReports: reports.clusterPolicyReports?.length || 0,
|
|
469
|
+
results: reports.results || []
|
|
470
|
+
},
|
|
471
|
+
violations,
|
|
472
|
+
degraded: kyverno.degraded || [],
|
|
473
|
+
emptyState: mode === 'disabled' ? 'Kyverno integration is disabled. Krate native RefPolicy and BranchProtection remain available.' : detected ? '' : 'Kyverno is not installed or is not readable by Krate.'
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function policySummary(resource = {}) {
|
|
478
|
+
return {
|
|
479
|
+
kind: resource.kind,
|
|
480
|
+
name: resource.metadata?.name,
|
|
481
|
+
namespace: resource.metadata?.namespace,
|
|
482
|
+
mode: resource.spec?.mode || resource.status?.mode || 'audit',
|
|
483
|
+
phase: resource.status?.phase || 'Pending',
|
|
484
|
+
displayName: resource.spec?.displayName || resource.metadata?.name,
|
|
485
|
+
targetKinds: resource.spec?.targetKinds || resource.spec?.match?.resourceKinds || [],
|
|
486
|
+
templateRef: resource.spec?.templateRef || '',
|
|
487
|
+
policyRef: resource.status?.policyRef || resource.spec?.policyRef || null,
|
|
488
|
+
violationCount: resource.status?.lastViolationCount || 0
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function createDeliveryView({ applications, releases, components, workloads, traits, scopes, policyDefinitions, policies, automationSteps, automations, managedResources }) {
|
|
493
|
+
return {
|
|
494
|
+
installed: applications.length + components.length + workloads.length + traits.length + scopes.length + policyDefinitions.length + automationSteps.length > 0,
|
|
495
|
+
specVersion: 'v0.3.0',
|
|
496
|
+
counts: {
|
|
497
|
+
applications: applications.length,
|
|
498
|
+
releases: releases.length,
|
|
499
|
+
components: components.length,
|
|
500
|
+
workloads: workloads.length,
|
|
501
|
+
traits: traits.length,
|
|
502
|
+
scopes: scopes.length,
|
|
503
|
+
policyDefinitions: policyDefinitions.length,
|
|
504
|
+
policies: policies.length,
|
|
505
|
+
automationSteps: automationSteps.length,
|
|
506
|
+
automations: automations.length,
|
|
507
|
+
managedResources: managedResources.length
|
|
508
|
+
},
|
|
509
|
+
capabilityCatalog: {
|
|
510
|
+
components: components.map((item) => item.metadata?.name).filter(Boolean),
|
|
511
|
+
workloads: workloads.map((item) => item.metadata?.name).filter(Boolean),
|
|
512
|
+
traits: traits.map((item) => item.metadata?.name).filter(Boolean),
|
|
513
|
+
scopes: scopes.map((item) => item.metadata?.name).filter(Boolean),
|
|
514
|
+
policyDefinitions: policyDefinitions.map((item) => item.metadata?.name).filter(Boolean),
|
|
515
|
+
policies: policies.map((item) => item.metadata?.name).filter(Boolean),
|
|
516
|
+
automationSteps: automationSteps.map((item) => item.metadata?.name).filter(Boolean)
|
|
517
|
+
},
|
|
518
|
+
applications: applications.map((application) => ({
|
|
519
|
+
name: application.metadata?.name,
|
|
520
|
+
namespace: application.metadata?.namespace,
|
|
521
|
+
healthy: application.status?.services?.every((service) => service.healthy !== false) || false,
|
|
522
|
+
message: application.status?.services?.map((service) => service.message).filter(Boolean).join('; ') || '',
|
|
523
|
+
status: application.status?.status || application.status?.phase || application.status?.conditions?.[0]?.type || 'Unknown',
|
|
524
|
+
appliedResources: (application.status?.appliedResources || []).map((resource) => ({ apiVersion: resource.apiVersion, kind: resource.kind, namespace: resource.namespace || '', name: resource.name })),
|
|
525
|
+
services: (application.status?.services || []).map((service) => ({ name: service.name, namespace: service.namespace, healthy: service.healthy !== false, message: service.message || '', traits: service.traits || [], workloadDefinition: service.workloadDefinition || null })),
|
|
526
|
+
workflow: application.status?.workflow ? { status: application.status.workflow.status || 'Unknown', finished: Boolean(application.status.workflow.finished), appRevision: application.status.workflow.appRevision || '', steps: (application.status.workflow.steps || []).map((step) => ({ name: step.name, type: step.type, phase: step.phase || 'Unknown' })) } : null,
|
|
527
|
+
releases: releases.filter((release) => release.metadata?.labels?.['app.oam.dev/name'] === application.metadata?.name || release.metadata?.name?.startsWith(application.metadata?.name + '-')).map((release) => ({ name: release.metadata?.name, succeeded: release.status?.succeeded, publishVersion: release.status?.publishVersion || '' })),
|
|
528
|
+
managedResources: managedResources.filter((resource) => resource.metadata?.labels?.['app.oam.dev/name'] === application.metadata?.name || resource.spec?.application?.name === application.metadata?.name || resource.metadata?.name?.startsWith(application.metadata?.name + '-')).map((resource) => resource.metadata?.name).filter(Boolean),
|
|
529
|
+
yaml: resourceToYaml(application)
|
|
530
|
+
})),
|
|
531
|
+
runtime: {
|
|
532
|
+
releases: releases.map((item) => ({ name: item.metadata?.name, application: item.metadata?.labels?.['app.oam.dev/name'] || item.spec?.application?.name || '', succeeded: item.status?.succeeded || false })),
|
|
533
|
+
automations: automations.map((item) => ({ name: item.metadata?.name, phase: item.status?.phase || item.status?.status || 'Unknown' })),
|
|
534
|
+
policies: policies.map((item) => ({ name: item.metadata?.name, type: item.type || item.spec?.type || item.metadata?.labels?.['policy.oam.dev/type'] || '' })),
|
|
535
|
+
managedResources: managedResources.map((item) => ({ name: item.metadata?.name, type: item.spec?.type || item.metadata?.labels?.['app.oam.dev/resource-tracker-type'] || '' }))
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
}
|