@givolt/i3-composer-agent 2.2.3 → 2.3.1
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/.agents/plugins/marketplace.json +1 -1
- package/.claude/commands/i3-prototype.md +23 -0
- package/.claude/commands/i3-review.md +1 -1
- package/.claude/commands/i3-ship.md +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.gemini/commands/i3-prototype.toml +23 -0
- package/.gemini/commands/i3-review.toml +1 -1
- package/.gemini/commands/i3-ship.toml +1 -1
- package/README.md +15 -5
- package/agents/code-reviewer.md +1 -1
- package/agents/test-engineer.md +1 -1
- package/bin/native-layout.cjs +2 -0
- package/commands/i3-prototype.toml +23 -0
- package/commands/i3-review.toml +1 -1
- package/commands/i3-ship.toml +1 -1
- package/docs/agents.md +2 -2
- package/docs/codebuddy-setup.md +2 -2
- package/docs/codex-setup.md +1 -1
- package/docs/copilot-setup.md +4 -0
- package/docs/cursor-setup.md +3 -0
- package/docs/other-hosts.md +44 -9
- package/docs/workbuddy-setup.md +2 -2
- package/evals/README.md +2 -0
- package/evals/cases/i3-documentation-and-adrs.json +18 -0
- package/evals/cases/i3-test-driven-development.json +8 -0
- package/evals/cases/i3-ui-prototype.json +49 -0
- package/evals/fixtures/i3-documentation-and-adrs/prose-review/README.md +19 -0
- package/evals/fixtures/i3-documentation-and-adrs/prose-review/queue.js +32 -0
- package/evals/fixtures/i3-ui-prototype/business-brief.md +7 -0
- package/evals/plugin/tdd-fires-on-pasted-bug/graders/failing-test-first.md +7 -0
- package/evals/plugin/tdd-fires-on-pasted-bug/graders/skill-fired.md +5 -0
- package/evals/plugin/tdd-fires-on-pasted-bug/prompt.md +15 -0
- package/evals/plugin/tdd-stays-quiet-on-debug/graders/not-fired.md +8 -0
- package/evals/plugin/tdd-stays-quiet-on-debug/prompt.md +25 -0
- package/evals/zh-CN/cases/i3-documentation-and-adrs.json +19 -0
- package/evals/zh-CN/cases/i3-test-driven-development.json +4 -0
- package/evals/zh-CN/cases/i3-ui-prototype.json +50 -0
- package/hooks/SIMPLIFY-IGNORE.md +3 -2
- package/hooks/simplify-ignore-test.sh +93 -1
- package/hooks/simplify-ignore.sh +94 -11
- package/package.json +31 -1
- package/plugin.json +1 -1
- package/references/performance-checklist.md +4 -2
- package/scripts/design-provider-discovery-test.js +190 -0
- package/scripts/design-provider-test.js +68 -0
- package/scripts/enterprise-template-test.js +179 -0
- package/scripts/lib/skill-lint-test.js +113 -1
- package/scripts/lib/skill-lint.js +62 -8
- package/scripts/run-evals-test.js +207 -0
- package/scripts/run-evals.js +47 -9
- package/scripts/template-adapter-test.js +155 -0
- package/scripts/ui-prototype-test.js +114 -0
- package/scripts/validate-commands-test.js +30 -0
- package/scripts/validate-commands.js +6 -16
- package/scripts/validate-reference-links-test.js +18 -0
- package/scripts/validate-reference-links.js +4 -2
- package/scripts/validate-skills.js +26 -3
- package/skills/i3-documentation-and-adrs/SKILL.md +9 -0
- package/skills/i3-frontend-ui-engineering/SKILL.md +5 -1
- package/skills/i3-idea-refine/SKILL.md +20 -5
- package/skills/i3-incremental-implementation/SKILL.md +4 -0
- package/skills/i3-performance-optimization/SKILL.md +10 -10
- package/skills/i3-security-and-hardening/references/hardening-patterns.md +1 -1
- package/skills/i3-shipping-and-launch/SKILL.md +6 -0
- package/skills/i3-test-driven-development/SKILL.md +3 -3
- package/skills/i3-ui-prototype/SKILL.md +119 -0
- package/skills/i3-ui-prototype/assets/templates/catalog.json +148 -0
- package/skills/i3-ui-prototype/assets/templates/classic-admin/manifest.json +9 -0
- package/skills/i3-ui-prototype/assets/templates/classic-admin/preset.json +6 -0
- package/skills/i3-ui-prototype/assets/templates/common/app/app.mjs +89 -0
- package/skills/i3-ui-prototype/assets/templates/common/app/config.json +1 -0
- package/skills/i3-ui-prototype/assets/templates/common/app/domain.mjs +20 -0
- package/skills/i3-ui-prototype/assets/templates/common/app/index.html +17 -0
- package/skills/i3-ui-prototype/assets/templates/common/app/serve.cjs +16 -0
- package/skills/i3-ui-prototype/assets/templates/common/app/styles.css +670 -0
- package/skills/i3-ui-prototype/assets/templates/common/app/tokens.css +19 -0
- package/skills/i3-ui-prototype/assets/templates/common/design-system/master.md +20 -0
- package/skills/i3-ui-prototype/assets/templates/common/gallery.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/common/mocks/applications.json +5 -0
- package/skills/i3-ui-prototype/assets/templates/common/requirements-map.json +8 -0
- package/skills/i3-ui-prototype/assets/templates/common/serve-gallery.cjs +16 -0
- package/skills/i3-ui-prototype/assets/templates/credits.md +17 -0
- package/skills/i3-ui-prototype/assets/templates/data-operations/manifest.json +9 -0
- package/skills/i3-ui-prototype/assets/templates/data-operations/preset.json +6 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/design-system.md +32 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/index.html +29 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/manifest.json +9 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/origin.json +344 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/preset.json +928 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/app.js +22 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/assets/empty.svg +1 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/assets/reference-styles.json +1 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/assets/result-403.svg +1 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/assets/result-404.svg +1 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/assets-manifest.json +334 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/baseline.json +173 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/conversion.md +30 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/credits.md +17 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/design-tokens.json +881 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/index.html +28 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/licenses/@qin-ui-antdv-next-pro.txt +9 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/licenses/ant-design-icons.txt +22 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/licenses/antdv-next.txt +21 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/package.json +12 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/qa/browser-routes.json +298 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/qa/coverage.json +180 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/qa/integrity.json +13 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/qa/keyboard.json +11 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/qa/responsive.json +125 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/qa/review.md +51 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/qa/rules.log +150 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/qa/screenshots/flow-editor-html.png +0 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/qa/screenshots/flow-editor-original.png +0 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/qa/screenshots/intent-create-html.png +0 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/qa/screenshots/intent-create-original.png +0 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/qa/screenshots/models-html.png +0 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/qa/screenshots/models-original.png +0 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/qa/screenshots/workbench-html.png +0 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/qa/screenshots/workbench-original.png +0 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/qa/selection.json +9 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/qa/visual-comparison.json +342 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/readme.md +73 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/reference.md +59 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/routes.json +178 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/runtime/controls.js +99 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/runtime/dialogs.js +54 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/runtime/dom.js +264 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/runtime/forms.js +47 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/runtime/icons.js +41 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/runtime/layout.js +47 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/runtime/router.js +63 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/runtime/state.js +90 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/runtime/table.js +41 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/runtime/widgets.js +8 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/agents/AgentDetail.html +3 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/agents/AgentDetail.js +36 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/agents/AgentEditor.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/agents/AgentEditor.js +51 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/agents/AgentsPage.html +3 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/agents/AgentsPage.js +48 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/agents/CheckResults.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/agents/CheckResults.js +6 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/agents/ResourcePicker.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/agents/ResourcePicker.js +13 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/agents/data.js +24 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/agents/domain.js +32 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/agents/registry.js +25 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/agents/store.js +85 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentClose.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentClose.js +17 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentCreate.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentCreate.js +66 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentDecisions.html +3 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentDecisions.js +38 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentDetail.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentDetail.js +66 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentDocument.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentDocument.js +5 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentHeader.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentHeader.js +12 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentHistory.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentHistory.js +11 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentList.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentList.js +116 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentReplan.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentReplan.js +32 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentRevision.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentRevision.js +49 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentSummary.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentSummary.js +10 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentWorkspace.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/IntentWorkspace.js +77 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/data.js +60 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/domain.js +105 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/intents/store.js +212 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/AuditPanel.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/AuditPanel.js +18 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/ModelDetail.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/ModelDetail.js +23 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/ModelEditor.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/ModelEditor.js +78 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/ModelGrant.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/ModelGrant.js +55 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/ModelReferences.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/ModelReferences.js +26 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/ModelRelease.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/ModelRelease.js +38 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/ModelStatus.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/ModelStatus.js +7 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/ModelTable.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/ModelTable.js +72 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/ModelTester.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/ModelTester.js +46 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/ModelsPage.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/ModelsPage.js +52 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/RowActions.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/RowActions.js +19 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/UsagePanel.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/UsagePanel.js +30 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/data.js +48 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/domain.js +39 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/models/store.js +129 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/IncidentDetail.html +4 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/IncidentDetail.js +21 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/IntentSource.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/IntentSource.js +8 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/OperationActionDialog.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/OperationActionDialog.js +37 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/OperationEntry.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/OperationEntry.js +10 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/OperationsPage.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/OperationsPage.js +37 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/RepairTrack.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/RepairTrack.js +49 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/RunDetail.html +4 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/RunDetail.js +22 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/SourceSummary.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/SourceSummary.js +9 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/commands.js +82 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/examples.js +47 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/model.js +61 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/repair.js +68 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/store.js +55 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/types.js +1 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/operations/workbench.js +18 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/ManagementTable.html +3 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/ManagementTable.js +28 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcCreateEntry.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcCreateEntry.js +18 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcDetail.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcDetail.js +31 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcEditor.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcEditor.js +27 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcLifecycle.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcLifecycle.js +16 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcPage.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcPage.js +43 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcPicker.html +4 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcPicker.js +34 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcProgress.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcProgress.js +30 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcWizard.html +3 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcWizard.js +67 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcWorkspace.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcWorkspace.js +41 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcsPage.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/PbcsPage.js +31 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/ServiceEditor.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/ServiceEditor.js +26 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/ServiceList.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/ServiceList.js +70 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/data.js +8 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/domain.js +62 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/management.js +36 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/managementStore.js +49 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/pbc/store.js +132 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/AdminList.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/AdminList.js +38 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/EffectiveGrants.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/EffectiveGrants.js +11 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/GrantEditor.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/GrantEditor.js +45 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/MemberEditor.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/MemberEditor.js +54 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/MembersPage.html +3 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/MembersPage.js +27 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/RecordsPage.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/RecordsPage.js +12 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/RoleEditor.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/RoleEditor.js +45 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/RolesPage.html +3 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/RolesPage.js +38 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/domain.js +61 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/state.js +51 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/store.js +116 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/releases/ReleaseActionDialog.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/releases/ReleaseActionDialog.js +45 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/releases/ReleaseDetail.html +4 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/releases/ReleaseDetail.js +30 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/releases/ReleaseEditor.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/releases/ReleaseEditor.js +67 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/releases/ReleaseExecution.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/releases/ReleaseExecution.js +5 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/releases/ReleasesPage.html +4 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/releases/ReleasesPage.js +60 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/releases/commands.js +157 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/releases/model.js +136 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/releases/store.js +76 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/reviews/ReviewDetail.html +3 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/reviews/ReviewDetail.js +26 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/reviews/ReviewEvidence.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/reviews/ReviewEvidence.js +14 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/reviews/ReviewProcess.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/reviews/ReviewProcess.js +113 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/reviews/ReviewsPage.html +3 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/reviews/ReviewsPage.js +62 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/reviews/commands.js +97 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/reviews/model.js +147 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/reviews/store.js +13 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shared/ColumnSettings.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shared/ColumnSettings.js +9 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shared/ListBatchBar.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shared/ListBatchBar.js +8 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shared/ListDeleteDialog.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shared/ListDeleteDialog.js +37 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shared/ListMore.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shared/ListMore.js +8 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shared/RecordMetadata.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shared/RecordMetadata.js +17 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shared/columns.js +32 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shared/deliveryGuide.js +49 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shared/deliveryView.js +12 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shared/listBatch.js +15 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shared/listSelection.js +37 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shared/policy.js +68 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shared/session.js +44 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shell/ConfirmationDialog.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shell/ConfirmationDialog.js +25 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shell/ConsoleShell.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shell/ConsoleShell.js +53 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shell/PrototypeGuide.html +10 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shell/PrototypeGuide.js +30 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shell/ScopePage.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shell/ScopePage.js +10 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shell/SessionDialog.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shell/SessionDialog.js +9 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shell/confirmation.js +3 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/shell/navigation.js +19 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/specs/SpecDocument.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/specs/SpecDocument.js +8 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/specs/SpecEdit.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/specs/SpecEdit.js +32 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/specs/SpecHeader.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/specs/SpecHeader.js +15 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/specs/SpecHistory.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/specs/SpecHistory.js +11 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/specs/SpecReview.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/specs/SpecReview.js +57 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/specs/SpecWorkspace.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/specs/SpecWorkspace.js +37 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/specs/data.js +20 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/specs/domain.js +55 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/specs/store.js +151 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/tasks/ArtifactDetail.html +3 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/tasks/ArtifactDetail.js +18 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/tasks/TaskCreate.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/tasks/TaskCreate.js +40 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/tasks/TaskDetail.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/tasks/TaskDetail.js +51 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/tasks/TaskLaunch.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/tasks/TaskLaunch.js +9 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/tasks/TasksPage.html +5 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/tasks/TasksPage.js +48 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/tasks/domain.js +204 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/tasks/exampleSource.js +22 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/tasks/registry.js +135 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/tasks/store.js +137 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/workbench/WorkbenchPage.html +3 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/workbench/WorkbenchPage.js +36 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/workbench/model.js +56 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/workbench/store.js +15 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/workflows/FlowCanvas.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/workflows/FlowCanvas.js +27 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/workflows/FlowDetail.html +4 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/workflows/FlowDetail.js +31 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/workflows/FlowEditor.html +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/workflows/FlowEditor.js +68 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/workflows/WorkflowsPage.html +5 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/workflows/WorkflowsPage.js +55 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/workflows/data.js +16 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/workflows/domain.js +83 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/workflows/registry.js +18 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/workflows/store.js +61 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/styles/admin.css +60 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/styles/agents.css +46 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/styles/antdv.css +295 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/styles/app.css +102 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/styles/delivery.css +4 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/styles/intents.css +37 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/styles/native.css +22 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/styles/operations.css +2 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/styles/pbc.css +3 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/styles/permissions.css +22 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/styles/pro.css +1 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/styles/releases.css +4 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/styles/reset.css +246 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/styles/reviews.css +8 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/styles/tasks.css +6 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/styles/tokens.css +15 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/styles/workbench.css +61 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/styles/workflows.css +1 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/admin-rules.test.js +96 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/agents.test.js +14 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/intent-state.test.js +37 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/list-batch.test.js +44 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/operation-fixtures.js +19 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/operation-repair.test.js +14 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/operations.test.js +25 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/pbc-management.test.js +17 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/pbc-state.test.js +24 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/permission-config.test.js +23 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/permissions.test.js +24 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/releases.test.js +50 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/reviews.test.js +36 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/runtime-forms-router.test.js +28 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/runtime-state.test.js +42 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/runtime-stores.test.js +16 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/spec-state.test.js +11 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/tasks.test.js +27 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/workbench.test.js +34 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tests/workflows.test.js +16 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tools/convert.mjs +168 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tools/serve.mjs +7 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tools/styles.mjs +11 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/tools/verify.mjs +24 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/serve.cjs +16 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/usability.css +44 -0
- package/skills/i3-ui-prototype/assets/templates/gallery/index.html +40 -0
- package/skills/i3-ui-prototype/assets/templates/modern-workspace/manifest.json +9 -0
- package/skills/i3-ui-prototype/assets/templates/modern-workspace/preset.json +6 -0
- package/skills/i3-ui-prototype/assets/templates/serve-gallery.cjs +23 -0
- package/skills/i3-ui-prototype/references/artifact-contract.md +41 -0
- package/skills/i3-ui-prototype/references/enterprise-admin.md +38 -0
- package/skills/i3-ui-prototype/references/expert-adapter.md +48 -0
- package/skills/i3-ui-prototype/references/template-adapters.md +57 -0
- package/skills/i3-ui-prototype/references/workflow-contract.md +19 -0
- package/skills/i3-ui-prototype/scripts/adapters/easel-html.cjs +30 -0
- package/skills/i3-ui-prototype/scripts/adapters/legacy-html.cjs +28 -0
- package/skills/i3-ui-prototype/scripts/check-design-provider.cjs +9 -0
- package/skills/i3-ui-prototype/scripts/check-prototype.cjs +14 -0
- package/skills/i3-ui-prototype/scripts/design-provider-discovery.cjs +128 -0
- package/skills/i3-ui-prototype/scripts/enterprise-template.cjs +97 -0
- package/skills/i3-ui-prototype/scripts/init-prototype.cjs +8 -0
- package/skills/i3-ui-prototype/scripts/manage-baseline.cjs +11 -0
- package/skills/i3-ui-prototype/scripts/prototype-lib.cjs +259 -0
- package/skills/i3-ui-prototype/scripts/template-adapters.cjs +89 -0
- package/skills/i3-using-agent-skills/SKILL.md +5 -0
package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/domain.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { can } from "../shared/policy.js";
|
|
2
|
+
export const clone = (value) => JSON.parse(JSON.stringify(value));
|
|
3
|
+
export function resolveActor(base, state) {
|
|
4
|
+
const roleIds = new Set(state.members.filter(m => m.actorId === base.id && !m.isDeleted).flatMap(m => m.roleIds));
|
|
5
|
+
const roles = state.roles.filter(r => roleIds.has(r.id) && r.enabled && !r.isDeleted);
|
|
6
|
+
return { ...base, label: roles.map(r => r.name).join('、') || '未分配业务角色', grants: roles.flatMap(r => r.grants.map(g => ({ ...clone(g), id: r.id + ':' + g.id, name: r.name + ' / ' + g.name }))) };
|
|
7
|
+
}
|
|
8
|
+
const subset = (values, allowed) => allowed.includes('*') || values.every(v => v !== '*' && allowed.includes(v));
|
|
9
|
+
export function checkDelegation(actor, role) {
|
|
10
|
+
if (!can(actor, 'permission:grant', role.tenantId))
|
|
11
|
+
throw Error('没有配置权限,或对象超出管理范围。');
|
|
12
|
+
for (const g of role.grants) {
|
|
13
|
+
if (!g.name.trim())
|
|
14
|
+
throw Error('请填写授权组名称。');
|
|
15
|
+
if (g.environmentIds && (!g.environmentIds.length || g.environmentIds.some(e => !['*', 'staging', 'production'].includes(e))))
|
|
16
|
+
throw Error('请选择有效的目标环境范围。');
|
|
17
|
+
if (!g.actions.length || !g.tenantIds.length || !g.pbcIds?.length)
|
|
18
|
+
throw Error('每个授权组都需要选择功能、租户和 PBC 范围。');
|
|
19
|
+
if (role.tenantId !== 'platform' && g.tenantIds.some(t => t !== role.tenantId))
|
|
20
|
+
throw Error('租户角色的授权不能超出所属租户。');
|
|
21
|
+
if (!actor.grants.some(a => a.actions.includes('permission:grant') && subset(g.tenantIds, a.tenantIds) && subset(g.pbcIds, a.pbcIds || ['*']) && subset(g.environmentIds || ['*'], a.environmentIds || ['*'])))
|
|
22
|
+
throw Error('拟授出的权限范围超出可管理范围。');
|
|
23
|
+
if (g.actions.some(a => a.startsWith('permission:')) && !actor.grants.some(a => a.actions.includes('permission:delegate') && subset(g.tenantIds, a.tenantIds) && subset(g.pbcIds, a.pbcIds || ['*'])))
|
|
24
|
+
throw Error('没有转授权限管理能力的授权。');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function assertVersion(record, expected) {
|
|
28
|
+
if (!record || record.isDeleted)
|
|
29
|
+
throw Error('记录已不存在,请刷新。');
|
|
30
|
+
if (record.recordVersion !== expected)
|
|
31
|
+
throw Error('记录已被更新,请重新打开并核对最新内容。');
|
|
32
|
+
}
|
|
33
|
+
export function validateRole(role, roles) {
|
|
34
|
+
if (!role.name.trim() || role.name.length > 40 || !role.description.trim())
|
|
35
|
+
throw Error('请填写角色名称与用途说明,名称不超过 40 个字符。');
|
|
36
|
+
if (!/^[a-z][a-z0-9-]{1,39}$/.test(role.code))
|
|
37
|
+
throw Error('角色编码使用 2—40 位小写字母、数字或短横线,以字母开头。');
|
|
38
|
+
if (roles.some(r => r.id !== role.id && !r.isDeleted && r.tenantId === role.tenantId && r.code === role.code))
|
|
39
|
+
throw Error('所属范围内的角色编码已存在。');
|
|
40
|
+
}
|
|
41
|
+
export function deletableRole(role, members) {
|
|
42
|
+
if (role.protected)
|
|
43
|
+
throw Error('系统引导角色受保护,不能删除。');
|
|
44
|
+
const count = members.filter(m => !m.isDeleted && m.roleIds.includes(role.id)).length;
|
|
45
|
+
if (count)
|
|
46
|
+
throw Error('仍有关联成员 ' + count + ' 人,请先调整成员授权。');
|
|
47
|
+
}
|
|
48
|
+
export function checkMembership(actor, member, roles, before) {
|
|
49
|
+
if (!can(actor, 'permission:members', member.tenantId))
|
|
50
|
+
throw Error('没有成员管理权限,或对象超出管理范围。');
|
|
51
|
+
if (!member.roleIds.length)
|
|
52
|
+
throw Error('请选择至少一个角色;取消全部授权请使用移除业务关联。');
|
|
53
|
+
if (before?.roleIds.some(id => roles.find(r => r.id === id)?.protected && !member.roleIds.includes(id)))
|
|
54
|
+
throw Error('系统引导管理员关联受保护。');
|
|
55
|
+
for (const id of member.roleIds) {
|
|
56
|
+
const role = roles.find(r => r.id === id && !r.isDeleted && r.enabled && r.tenantId === member.tenantId);
|
|
57
|
+
if (!role)
|
|
58
|
+
throw Error('角色已停用、不存在或所属范围不同。');
|
|
59
|
+
checkDelegation(actor, role);
|
|
60
|
+
}
|
|
61
|
+
}
|
package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/state.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { reactive } from '../../runtime/state.js';
|
|
2
|
+
import { actors, metadata, actionGroups } from "../shared/policy.js";
|
|
3
|
+
import { clone } from "./domain.js";
|
|
4
|
+
export const authorityKey = 'i3-prototype-antdv-v010-authority';
|
|
5
|
+
export function seedAuthority() {
|
|
6
|
+
const at = '2026-09-10T08:00:00.000Z', admin = actors[0], roles = [], members = [];
|
|
7
|
+
for (const actor of actors) {
|
|
8
|
+
for (const g of actor.grants.filter(g => g.actions.length)) {
|
|
9
|
+
const tenantId = g.tenantIds.includes('*') || g.tenantIds.includes('platform') ? 'platform' : actor.tenantId;
|
|
10
|
+
const id = 'role-' + actor.id + '-' + g.id;
|
|
11
|
+
roles.push({ ...metadata(admin, at, tenantId), id, name: g.name, code: actor.id + '-' + g.id, description: '沿用业务原型的示例角色,可按管理权限调整。', enabled: true, protected: false, grants: [{ ...clone(g), pbcIds: g.pbcIds || ['*'] }] });
|
|
12
|
+
let m = members.find(m => m.actorId === actor.id && m.tenantId === tenantId);
|
|
13
|
+
if (!m) {
|
|
14
|
+
m = { ...metadata(admin, at, tenantId), id: actor.id + ':' + tenantId, actorId: actor.id, roleIds: [] };
|
|
15
|
+
members.push(m);
|
|
16
|
+
}
|
|
17
|
+
m.roleIds.push(id);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const management = actionGroups.find(g => g.name === '权限管理').items.map(i => i.value);
|
|
21
|
+
roles.unshift({ ...metadata(admin, at, 'platform'), id: 'system-admin', name: '权限管理员', code: 'permission-admin', description: '管理平台及授权范围内的业务权限;原型引导角色。', enabled: true, protected: true, grants: [{ id: 'admin', name: '权限管理', actions: management, tenantIds: ['*'], pbcIds: ['*'] }] });
|
|
22
|
+
members.find(m => m.actorId === admin.id && m.tenantId === 'platform').roleIds.push('system-admin');
|
|
23
|
+
roles.push({ ...metadata(admin, at, 'heating'), id: 'heating-manager', name: '供热权限维护员', code: 'heating-access-manager', description: '只能维护供热范围的业务授权,不能转授管理权限。', enabled: true, protected: false, grants: [{ id: 'limited', name: '供热管理范围', actions: management.filter(a => a !== 'permission:delegate'), tenantIds: ['heating'], pbcIds: ['*'] }] });
|
|
24
|
+
return { version: 1, roles, members, records: [] };
|
|
25
|
+
}
|
|
26
|
+
let initial = seedAuthority();
|
|
27
|
+
try {
|
|
28
|
+
const saved = JSON.parse(localStorage.getItem(authorityKey) || 'null');
|
|
29
|
+
if (saved?.version && Array.isArray(saved.roles) && Array.isArray(saved.members) && Array.isArray(saved.records))
|
|
30
|
+
initial = saved;
|
|
31
|
+
}
|
|
32
|
+
catch { /* Resettable local prototype. */ }
|
|
33
|
+
export const authority = reactive(initial);
|
|
34
|
+
export function persistAuthority(next) {
|
|
35
|
+
try {
|
|
36
|
+
localStorage.setItem(authorityKey, JSON.stringify(next));
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw Error('浏览器无法保存本次授权,原权限保持不变。');
|
|
40
|
+
}
|
|
41
|
+
Object.assign(authority, next);
|
|
42
|
+
}
|
|
43
|
+
if (typeof window !== 'undefined')
|
|
44
|
+
window.addEventListener('storage', event => { if (event.key === authorityKey && event.newValue) {
|
|
45
|
+
try {
|
|
46
|
+
const data = JSON.parse(event.newValue);
|
|
47
|
+
if (data.version > authority.version)
|
|
48
|
+
Object.assign(authority, data);
|
|
49
|
+
}
|
|
50
|
+
catch { /* invalid local event ignored */ }
|
|
51
|
+
} });
|
package/skills/i3-ui-prototype/assets/templates/enterprise-admin/reference/src/permissions/store.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { computed, ref } from '../../runtime/state.js';
|
|
2
|
+
import { defineStore } from "../../runtime/state.js";
|
|
3
|
+
import { useSession } from "../shared/session.js";
|
|
4
|
+
import { actors, metadata, actionGroups, tenantLabel } from "../shared/policy.js";
|
|
5
|
+
import { authority, persistAuthority, seedAuthority } from "./state.js";
|
|
6
|
+
import { clone, resolveActor, validateRole, checkDelegation, checkMembership, deletableRole, assertVersion } from "./domain.js";
|
|
7
|
+
import { assertBatch } from "../shared/listBatch.js";
|
|
8
|
+
export const useAuthority = defineStore('authority', () => {
|
|
9
|
+
const session = useSession(), scene = ref('normal');
|
|
10
|
+
const roles = computed(() => authority.roles.filter(r => !r.isDeleted && session.can('permission:view', r.tenantId)));
|
|
11
|
+
const members = computed(() => authority.members.filter(m => !m.isDeleted && session.can('permission:view', m.tenantId)));
|
|
12
|
+
const records = computed(() => authority.records.filter(r => session.can('permission:audit', r.tenantId)));
|
|
13
|
+
const actorName = (id) => actors.find(a => a.id === id)?.name || id;
|
|
14
|
+
const roleName = (id) => authority.roles.find(r => r.id === id)?.name || id;
|
|
15
|
+
const touch = (r) => ({ ...r, recordVersion: r.recordVersion + 1, updatedBy: session.actor.name, updatedAt: new Date().toISOString() });
|
|
16
|
+
function describe(value) {
|
|
17
|
+
if (value === null || value === undefined)
|
|
18
|
+
return '无';
|
|
19
|
+
if (Array.isArray(value))
|
|
20
|
+
return value.length ? value.map(describe).join('\n\n') : '无';
|
|
21
|
+
if (typeof value !== 'object')
|
|
22
|
+
return String(value);
|
|
23
|
+
const v = value;
|
|
24
|
+
if (Array.isArray(v.actions))
|
|
25
|
+
return String(v.name) + ':' + v.actions.map(a => actionGroups.flatMap(g => g.items).find(i => i.value === a)?.label || a).join('、') + '\n范围:' + v.tenantIds.map(t => t === '*' ? '全部范围' : tenantLabel(t)).join('、') + '\nPBC:' + (v.pbcIds || ['*']).map(p => p === '*' ? '全部 PBC(含新建)' : ['供热工单', '客户服务', '能源台账', '知识助手', '运营报表', '收费管理', '巡检管理'][Number(p.replace('pbc-', ''))] || p).join('、');
|
|
26
|
+
const labels = { name: '名称', description: '用途说明', enabled: '角色状态', isDeleted: '业务记录已移除', member: '成员', roles: '关联角色' };
|
|
27
|
+
return Object.entries(v).filter(([k]) => k !== 'id').map(([k, x]) => (labels[k] || k) + ':' + (typeof x === 'boolean' ? (k === 'enabled' ? (x ? '启用' : '停用') : (x ? '是' : '否')) : describe(x))).join('\n');
|
|
28
|
+
}
|
|
29
|
+
function apply(operation, target, tenantId, reason, before, after, mutate) {
|
|
30
|
+
if (!reason.trim())
|
|
31
|
+
throw Error('请填写变更原因。');
|
|
32
|
+
session.writeCheck();
|
|
33
|
+
const next = clone(authority);
|
|
34
|
+
mutate(next);
|
|
35
|
+
next.version++;
|
|
36
|
+
next.records.unshift({ id: crypto.randomUUID(), actorId: session.actor.id, actorName: session.actor.name, at: new Date().toISOString(), tenantId, target, operation, reason, before: describe(before), after: describe(after) });
|
|
37
|
+
persistAuthority(next);
|
|
38
|
+
}
|
|
39
|
+
function saveRole(input, reason) {
|
|
40
|
+
const old = authority.roles.find(r => r.id === input.id);
|
|
41
|
+
session.require('permission:roles', input.tenantId);
|
|
42
|
+
if (old) {
|
|
43
|
+
session.require('permission:roles', old.tenantId);
|
|
44
|
+
assertVersion(old, input.recordVersion);
|
|
45
|
+
if (old.protected)
|
|
46
|
+
throw Error('系统引导角色受保护。');
|
|
47
|
+
if (input.tenantId !== old.tenantId || input.code !== old.code)
|
|
48
|
+
throw Error('角色创建后编码和所属范围不可修改。');
|
|
49
|
+
}
|
|
50
|
+
validateRole(input, authority.roles);
|
|
51
|
+
const fields = { name: input.name.trim(), code: input.code, description: input.description.trim() };
|
|
52
|
+
const next = old ? { ...touch(old), ...fields } : { ...metadata(session.actor, new Date().toISOString(), input.tenantId), ...fields, id: input.id, grants: [], enabled: true, protected: false };
|
|
53
|
+
apply(old ? '编辑角色' : '新增角色', input.name, input.tenantId, reason, old ? { name: old.name, description: old.description } : null, fields, s => { const i = s.roles.findIndex(r => r.id === next.id); if (i < 0)
|
|
54
|
+
s.roles.push(next);
|
|
55
|
+
else
|
|
56
|
+
s.roles[i] = next; });
|
|
57
|
+
}
|
|
58
|
+
function saveGrants(input, reason) {
|
|
59
|
+
const old = authority.roles.find(r => r.id === input.id);
|
|
60
|
+
assertVersion(old, input.recordVersion);
|
|
61
|
+
session.require('permission:grant', old.tenantId);
|
|
62
|
+
if (old.protected)
|
|
63
|
+
throw Error('系统引导角色受保护。');
|
|
64
|
+
const next = { ...touch(old), grants: clone(input.grants) };
|
|
65
|
+
checkDelegation(session.actor, next);
|
|
66
|
+
apply('配置权限', old.name, old.tenantId, reason, old.grants, next.grants, s => { s.roles[s.roles.findIndex(r => r.id === input.id)] = next; });
|
|
67
|
+
}
|
|
68
|
+
function toggleRole(role, reason) {
|
|
69
|
+
const old = authority.roles.find(r => r.id === role.id);
|
|
70
|
+
assertVersion(old, role.recordVersion);
|
|
71
|
+
session.require('permission:roles', old.tenantId);
|
|
72
|
+
if (old.protected)
|
|
73
|
+
throw Error('系统引导角色受保护。');
|
|
74
|
+
const next = { ...touch(old), enabled: !old.enabled };
|
|
75
|
+
apply(next.enabled ? '启用角色' : '停用角色', old.name, old.tenantId, reason, { enabled: old.enabled }, { enabled: next.enabled }, s => { s.roles[s.roles.findIndex(r => r.id === role.id)] = next; });
|
|
76
|
+
}
|
|
77
|
+
function sameScope(rows) { if (new Set(rows.map(r => r.tenantId)).size > 1)
|
|
78
|
+
throw Error('请按同一所属范围筛选后批量操作。'); }
|
|
79
|
+
function checkDeleteRole(t) { assertBatch(authority.roles, [t], r => { session.require('permission:delete', r.tenantId); deletableRole(r, authority.members); }); }
|
|
80
|
+
function deleteRoles(targets) {
|
|
81
|
+
const rows = assertBatch(authority.roles, targets, r => { session.require('permission:delete', r.tenantId); deletableRole(r, authority.members); });
|
|
82
|
+
sameScope(rows);
|
|
83
|
+
apply('删除角色', rows.map(r => r.name).join('、'), rows[0].tenantId, '确认删除所选角色', rows.map(r => ({ id: r.id, name: r.name })), { isDeleted: true }, s => { for (const row of rows)
|
|
84
|
+
s.roles[s.roles.findIndex(r => r.id === row.id)] = { ...touch(row), isDeleted: true }; });
|
|
85
|
+
}
|
|
86
|
+
function saveMember(input, reason) {
|
|
87
|
+
const old = authority.members.find(m => m.id === input.id);
|
|
88
|
+
session.require('permission:members', input.tenantId);
|
|
89
|
+
if (old && !old.isDeleted)
|
|
90
|
+
assertVersion(old, input.recordVersion);
|
|
91
|
+
if (!actors.some(a => a.id === input.actorId))
|
|
92
|
+
throw Error('身份目录中没有该成员。');
|
|
93
|
+
if (old && (old.actorId !== input.actorId || old.tenantId !== input.tenantId))
|
|
94
|
+
throw Error('成员和所属范围不可更换。');
|
|
95
|
+
if (authority.members.some(m => !m.isDeleted && m.id !== input.id && m.actorId === input.actorId && m.tenantId === input.tenantId))
|
|
96
|
+
throw Error('该成员在此范围已有关联,请编辑现有授权。');
|
|
97
|
+
checkMembership(session.actor, input, authority.roles, old);
|
|
98
|
+
const next = old ? { ...touch(old), roleIds: [...new Set(input.roleIds)], isDeleted: false } : { ...metadata(session.actor, new Date().toISOString(), input.tenantId), id: input.id, actorId: input.actorId, roleIds: [...new Set(input.roleIds)] };
|
|
99
|
+
apply(old && !old.isDeleted ? '修改成员授权' : '添加成员关联', actorName(input.actorId), input.tenantId, reason, old ? old.roleIds.map(roleName) : [], next.roleIds.map(roleName), s => { const i = s.members.findIndex(m => m.id === next.id); if (i < 0)
|
|
100
|
+
s.members.push(next);
|
|
101
|
+
else
|
|
102
|
+
s.members[i] = next; });
|
|
103
|
+
}
|
|
104
|
+
function checkDeleteMember(t) { assertBatch(authority.members, [t], m => { session.require('permission:delete', m.tenantId); session.require('permission:members', m.tenantId); if (m.roleIds.some(id => authority.roles.find(r => r.id === id)?.protected))
|
|
105
|
+
throw Error('系统引导管理员关联受保护。'); }); }
|
|
106
|
+
function deleteMembers(targets) {
|
|
107
|
+
targets.forEach(checkDeleteMember);
|
|
108
|
+
const rows = assertBatch(authority.members, targets, () => { });
|
|
109
|
+
sameScope(rows);
|
|
110
|
+
apply('移除成员关联', rows.map(m => actorName(m.actorId)).join('、'), rows[0].tenantId, '确认移除业务关联,保留身份账号', rows.map(m => ({ member: actorName(m.actorId), roles: m.roleIds.map(roleName) })), { isDeleted: true }, s => { for (const row of rows)
|
|
111
|
+
s.members[s.members.findIndex(m => m.id === row.id)] = { ...touch(row), isDeleted: true }; });
|
|
112
|
+
}
|
|
113
|
+
function effective(actorId, tenantId) { session.require('permission:view', tenantId); return resolveActor(actors.find(a => a.id === actorId), { ...authority, members: authority.members.filter(m => m.tenantId === tenantId) }); }
|
|
114
|
+
function reset() { const next = seedAuthority(); next.version = authority.version + 1; persistAuthority(next); scene.value = 'normal'; }
|
|
115
|
+
return { roles, members, records, scene, actorName, roleName, saveRole, saveGrants, toggleRole, checkDeleteRole, deleteRoles, saveMember, checkDeleteMember, deleteMembers, effective, reset };
|
|
116
|
+
});
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<!-- 来源:src/releases/ReleaseActionDialog.vue;HTML / 原生 DOM 绑定,不含 Vue 运行时。 -->
|
|
2
|
+
<template data-component="Modal" data-bind-open="(true)" data-bind-title="(actionLabels[action])" data-bind-width="(650)" data-bind-mask-closable="(false)" data-on-cancel="(emit('close'))"><div class="release-note"><strong>{{ (v.name) }} · {{ (v.version) }}</strong><p>{{ (environmentLabel(v.environment)) }} · 发布单修订 v{{ (v.number) }} · 配置 {{ (v.config) }}</p><p>制品 {{ (v.materials?.digest || '缺失') }} · 首次灰度 {{ (v.traffic) }}%</p></div><template data-component="Alert" data-if="(action === 'approve')" type="warning" show-icon="" data-bind-title="('此授权仅覆盖本版本在' + environmentLabel(v.environment) + '的首次灰度及后续全量')" description="变更制品、配置、环境或灰度方案后,必须重新检查和人工授权。" class="page-alert"></template><template data-component="Alert" data-if="(['rollback', 'stop'].includes(action))" class="page-alert" type="warning" show-icon="" data-bind-title="(action === 'rollback' ? '目标稳定版本:' + (v.materials?.rollback?.version || '无') : '停止本次发布并撤除本版本流量')" description="应用恢复不会自动还原数据库;按本版迁移与恢复方案单独核查。"></template><template data-component="Alert" data-if="(stale)" type="warning" show-icon="" title="记录已变化,操作说明保留"><template data-slot="action"><template data-component="Button" data-on-click="(rebase)">已核对当前版本</template></template></template><template data-component="Alert" data-if="(reason || error)" type="error" show-icon="" data-bind-title="(error || reason)" class="page-alert"></template><template data-component="ProForm" name="release-action" data-bind-form="(form)" layout="vertical" data-bind-disabled="(busy)"></template><template data-component="Checkbox" data-if="(action === 'approve')" data-model-checked="(checked)">已核对本版证据、目标环境、灰度阈值及恢复方案</template><p class="muted">关闭后保留本账号的说明;登录失效或版本变化时不会自动提交。</p><template data-slot="footer"><template data-component="Space" wrap=""><template data-component="Button" data-on-click="(emit('close'))">取消并保留说明</template><template data-component="Button" type="primary" data-bind-danger="(['rollback', 'stop'].includes(action))" data-bind-loading="(busy)" data-bind-disabled="(!!reason || stale || (action === 'approve' && !checked))" data-on-click="(submit)">{{ (actionLabels[action]) }}</template></template></template></template>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { computed, ref, watch } from '../../runtime/state.js';
|
|
2
|
+
import { Alert, Button, Checkbox, Modal, Space } from "../../runtime/widgets.js";
|
|
3
|
+
import { ProForm, useForm } from "../../runtime/widgets.js";
|
|
4
|
+
import { useReleases } from "./store.js";
|
|
5
|
+
import { actionLabels, versionOf, environmentLabel } from "./model.js";
|
|
6
|
+
import { createComponent } from '../../runtime/dom.js';
|
|
7
|
+
export default createComponent({ name: 'ReleaseActionDialog', propKeys: ["row", "action", "outcome"], template: new URL('./ReleaseActionDialog.html', import.meta.url).href, setup(__props, __emit) {
|
|
8
|
+
const props = __props, emit = __emit, store = useReleases(), busy = ref(false), error = ref(''), checked = ref(false);
|
|
9
|
+
const storage = 'i3-prototype-antdv-release-action:' + store.session.actor.id + ':' + props.row.id + ':' + props.action;
|
|
10
|
+
let saved;
|
|
11
|
+
try {
|
|
12
|
+
saved = JSON.parse(sessionStorage.getItem(storage) || 'null');
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
saved = null;
|
|
16
|
+
}
|
|
17
|
+
const expected = ref(saved?.expected ?? props.row.recordVersion), id = ref(saved?.id || crypto.randomUUID());
|
|
18
|
+
const form = useForm({ note: saved?.note || '' }, [{ path: 'note', label: '操作说明', component: 'textarea', span: 24, rows: 3, maxlength: 2000, rules: ['approve', 'return', 'pause', 'resume', 'retry', 'rollback', 'stop'].includes(props.action) ? [{ required: true, whitespace: true, message: '请填写操作说明' }] : [] }]);
|
|
19
|
+
const fresh = computed(() => store.find(props.row.id)), v = computed(() => versionOf(fresh.value?.release || props.row.release)), stale = computed(() => fresh.value?.recordVersion !== expected.value), reason = computed(() => fresh.value ? store.reason(fresh.value, props.action) : '记录不可访问。');
|
|
20
|
+
watch([() => form.formData.note, expected, id], () => { try {
|
|
21
|
+
sessionStorage.setItem(storage, JSON.stringify({ note: form.formData.note, expected: expected.value, id: id.value }));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
error.value = '输入缓存保存失败,请保留说明。';
|
|
25
|
+
} }, { flush: 'sync' });
|
|
26
|
+
function rebase() { expected.value = fresh.value.recordVersion; id.value = crypto.randomUUID(); checked.value = false; error.value = ''; }
|
|
27
|
+
async function submit() { try {
|
|
28
|
+
await form.formRef.value?.validate();
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return;
|
|
32
|
+
} if (busy.value)
|
|
33
|
+
return; busy.value = true; error.value = ''; try {
|
|
34
|
+
store.apply(props.row.task.id, props.row.id, { id: id.value, action: props.action, expected: expected.value, note: form.formData.note, checked: checked.value, outcome: props.outcome });
|
|
35
|
+
sessionStorage.removeItem(storage);
|
|
36
|
+
emit('saved');
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
error.value = e.message;
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
busy.value = false;
|
|
43
|
+
} }
|
|
44
|
+
return { computed, ref, watch, Alert, Button, Checkbox, Modal, Space, ProForm, useForm, useReleases, actionLabels, versionOf, environmentLabel, props, emit, store, busy, error, checked, storage, saved, expected, id, form, fresh, v, stale, reason, rebase, submit };
|
|
45
|
+
} });
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<!-- 来源:src/releases/ReleaseDetail.vue;HTML / 原生 DOM 绑定,不含 Vue 运行时。 -->
|
|
2
|
+
<template data-component="Result" data-if="(!row || !r || !v)" status="403" title="发布单不存在或无权访问"><template data-slot="extra"><template data-component="Button" data-on-click="(router.push('/releases'))">返回发布列表</template></template></template><template data-else=""><template data-component="OperationEntry" data-bind-task="(row.task)" data-bind-release-id="(r.id)"></template><div class="page-heading"><div><template data-component="Button" type="link" class="agent-back" data-on-click="(router.push('/releases'))">← 返回门禁与发布</template><div class="release-title"><h1>{{ (execution ? '发布执行与结果' : v.name) }}</h1><template data-component="Tag" data-bind-color="(r.unconfirmed ? 'orange' : releaseColors[r.state])">{{ (r.unconfirmed ? '结果待核实' : releaseLabels[r.state]) }}</template></div><p>{{ (r.code) }} · {{ (execution ? v.name + ' · ' : '') }}{{ (v.version) }} · {{ (environmentLabel(v.environment)) }} · 修订 v{{ (v.number) }}</p></div><template data-component="Space" wrap=""><template data-component="Button" data-on-click="(router.push('/tasks/' + row.task.id))">来源任务</template><template data-component="Button" data-on-click="(router.push(releaseUrl(r, !execution)))">{{ (execution ? '审核与版本证据' : '执行与结果') }}</template></template></div><template data-component="Alert" data-if="(r.unconfirmed)" class="page-alert" type="warning" show-icon="" title="上次提交响应不明,请查询原请求回执" data-bind-description="('原编号 ' + r.unconfirmed + ';查询结果前不会再次推进。')"></template><template data-component="Alert" data-if="(execution && !r.unconfirmed)" class="page-alert" data-bind-type="(r.state === 'failed' ? 'error' : r.state === 'succeeded' ? 'success' : 'info')" show-icon="" data-bind-title="(releaseSummary(r))" data-bind-description="(r.state === 'succeeded' ? '本次发布完成;运行验收与归档属于后续范围。' : '当前展示模拟部署和观测结果,每一步均保留原版本、当前流量及回执。')"></template><div class="release-layout"><section class="agent-panel release-content"><template data-if="(execution)"><template data-if="(!r.unconfirmed)"><ol class="release-steps" aria-label="发布阶段"><li data-each="(name,i) in (stages)" data-bind-key="(name)" data-bind-class="({ done: stepDone(i), current: i === step })"><span>{{ (i + 1) }}</span><strong>{{ (name) }}</strong></li></ol><div class="release-traffic"><div><span class="muted">本次版本 {{ (v.version) }} 的流量</span><strong>{{ (r.traffic) }}<small>%</small></strong><p>{{ (r.state === 'rolledBack' ? '已切回 ' + v.materials?.rollback?.version : r.state === 'stopped' ? '本次版本流量已撤除' : r.traffic === 100 ? '本次版本已接入全部流量' : r.traffic === 0 ? '新版本尚未接入流量' : '保留当前比例,按本版阈值判断后续动作') }}</p></div><template data-component="Progress" type="circle" data-bind-percent="(r.traffic)" data-bind-size="(140)" data-bind-status="(r.state === 'failed' ? 'exception' : 'normal')"></template></div><div class="release-stat-grid"><article><span>错误率</span><strong>{{ (r.observation ? r.observation.errorRate + '%' : '待灰度后采集') }}</strong><small>上限 {{ (v.errorLimit) }}%</small></article><article><span>P95 延迟</span><strong>{{ (r.observation ? r.observation.latency + ' ms' : '待灰度后采集') }}</strong><small>上限 {{ (v.latencyLimit) }} ms</small></article><article><span>有限重试</span><strong>{{ (r.retries) }} / 2</strong><small>只重试失败步骤</small></article></div><p data-if="(r.observation)" class="muted">观测时间 {{ (formatDate(r.observation.at)) }} · {{ (r.observation.pass ? '达到本版阈值' : '未达到本版阈值,禁止全量') }} · 指标均为模拟</p></template><template data-component="Alert" data-else="" type="info" show-icon="" title="本次提交的执行结果尚待核实" description="核实前不展示本次提交的阶段、流量或观测结论;已有执行记录保留,下方待核实回执不作为完成依据。" class="page-alert"></template><div class="release-note"><strong>{{ (v.materials?.rollback ? '稳定版本恢复目标:' + v.materials.rollback.version : '首次发布:暂无稳定版本可回滚') }}</strong><p>{{ (v.rollbackPlan) }}</p><p>{{ (v.materials?.migration === 'expand-contract' ? '本次包含兼容扩展迁移;应用回滚后,数据库保持当前迁移状态,需单独核查。' : '本次材料登记无数据库变更;停止或回滚仍须保留恢复记录。') }}</p></div><h2>执行与恢复记录</h2><ol class="release-events"><li data-each="event in ([...r.events].reverse())" data-bind-key="(event.id)"><div><strong>{{ (event.action) }}</strong><template data-component="Tag" data-if="(event.id === r.unconfirmed)" color="orange">待核实回执</template><template data-component="Tag">本版流量 {{ (event.traffic) }}%</template></div><p>{{ (event.detail) }}</p><small>{{ (event.actor) }} · {{ (formatDate(event.at)) }} · 修订 v{{ (event.version) }}</small><code>{{ (event.id) }}</code></li></ol></template><template data-else=""><template data-component="Tabs" data-model-active-key="(tab)" data-bind-items="([{ key: 'evidence', label: '发布前证据' }, { key: 'versions', label: '版本快照 (' + r.versions.length + ')' }, { key: 'history', label: '操作与授权记录' }])"></template><template data-if="(tab === 'evidence')"><div class="section-heading"><h2>发布前检查</h2><template data-component="Tag" data-bind-color="(checks.every(c => ['pass', 'na'].includes(c.status)) ? 'green' : 'orange')">{{ (checks.filter(c => !['pass', 'na'].includes(c.status)).length) }} 项待补齐</template></div><p class="muted">核对固定版本的材料;灰度观测在授权、部署及接入流量之后进行。</p><p data-if="(v.preflight)">最近检查:{{ (v.preflight.by) }} · {{ (formatDate(v.preflight.at)) }}</p><template data-component="Alert" data-else="" type="info" show-icon="" title="本修订版尚未执行发布前检查" class="page-alert"></template><div class="review-checks"><details data-each="c in (checks)" data-bind-key="(c.id)"><summary><template data-component="Tag" data-bind-color="(c.status === 'pass' || c.status === 'na' ? 'green' : c.status === 'fail' ? 'red' : 'orange')">{{ (c.status === 'pass' ? '通过' : c.status === 'na' ? '不适用' : c.status === 'fail' ? '失败' : '缺失') }}</template><strong>{{ (c.name) }}</strong></summary><p class="release-check-detail">{{ (c.detail) }}</p></details></div><h2 class="release-section-title">固定的交付产物</h2><div class="release-ref-list"><template data-component="RouterLink" data-each="ref in (v.refs)" data-bind-key="(ref.id)" data-bind-to="('/tasks/' + row.task.id + '/artifacts/' + ref.id + '?version=' + ref.version)">{{ (ref.name) }} v{{ (ref.version) }} →</template></div><h3>发布说明</h3><p class="review-text">{{ (v.description || '尚未填写') }}</p><h3>灰度与恢复方案</h3><p>首次 {{ (v.traffic) }}% → 观测达标 → 全量;错误率 ≤ {{ (v.errorLimit) }}%,P95 ≤ {{ (v.latencyLimit) }}ms。</p><p class="review-text">{{ (v.rollbackPlan || '尚未填写') }}</p></template><template data-else-if="(tab === 'versions')"><div class="section-heading"><h2>不可变版本依据</h2><template data-component="Select" data-model-value="(selectedVersion)" aria-label="发布修订版" data-bind-options="([{ value: 0, label: '当前修订 v' + v.number }, ...r.versions.filter(x => x.number !== v?.number).map(x => ({ value: x.number, label: '历史修订 v' + x.number }))])"></template></div><template data-if="(evidence)"><dl class="task-trace"><dt>应用 / 修订版本</dt><dd>{{ (evidence.version) }} / v{{ (evidence.number) }}</dd><dt>目标环境 / 配置</dt><dd>{{ (environmentLabel(evidence.environment)) }} / {{ (evidence.config) }}</dd><dt>来源任务 / 规格 / 流程</dt><dd>{{ (evidence.sourceCode) }} / v{{ (evidence.specVersion) }} / {{ (evidence.flowName) }} v{{ (evidence.flowVersion) }}</dd><dt>提交标识</dt><dd>{{ (evidence.materials?.commit || '缺失') }}</dd><dt>制品摘要</dt><dd class="mono">{{ (evidence.materials?.digest || '缺失') }}</dd><dt>发布说明</dt><dd class="review-text">{{ (evidence.description) }}</dd><dt>首次灰度与阈值</dt><dd>{{ (evidence.traffic) }}% · 错误率 ≤ {{ (evidence.errorLimit) }}% · P95 ≤ {{ (evidence.latencyLimit) }} ms</dd><dt>回退与恢复</dt><dd class="review-text">{{ (evidence.rollbackPlan) }}</dd><dt>修订人 / 时间</dt><dd>{{ (evidence.by) }} · {{ (formatDate(evidence.at)) }}</dd></dl><template data-component="Alert" data-if="(evidence.number !== v.number)" type="info" show-icon="" title="正在查看历史快照,不能用于当前发布授权"></template></template></template><ol data-else="" class="release-events"><li data-each="event in ([...r.events].reverse())" data-bind-key="(event.id)"><strong>{{ (event.action) }} · 修订 v{{ (event.version) }}</strong><p>{{ (event.detail) }}</p><small>{{ (event.actor) }} · {{ (formatDate(event.at)) }}</small><code>{{ (event.id) }}</code></li></ol></template></section><aside class="release-aside"><section class="agent-panel"><h2>{{ (execution ? '执行操作' : '当前待办') }}</h2><p>{{ (releaseSummary(r)) }}</p><div class="release-action-stack"><div data-each="a in (actions)" data-bind-key="(a)"><template data-component="Button" data-bind-type="(a === actions[0] ? 'primary' : 'default')" data-bind-disabled="(!!store.reason(row, a))" data-on-click="(action = a)">{{ (label(a)) }}</template><small data-if="(store.reason(row, a))">{{ (store.reason(row, a)) }}</small></div><template data-component="Button" data-if="(!execution && !r.startedAt)" data-bind-disabled="(!!store.reason(row, 'edit'))" data-on-click="(editing = true)">修改发布单</template><small data-if="(!execution && !r.startedAt && store.reason(row, 'edit'))">{{ (store.reason(row, 'edit')) }}</small><template data-if="(showRecovery)"><template data-component="Button" danger="" data-bind-disabled="(!!store.reason(row, 'rollback'))" data-on-click="(action = 'rollback')">回滚至稳定版本</template><small data-if="(store.reason(row, 'rollback'))">{{ (store.reason(row, 'rollback')) }}</small><template data-component="Button" danger="" data-bind-disabled="(!!store.reason(row, 'stop'))" data-on-click="(action = 'stop')">停止并撤流</template></template></div><p data-if="(!actions.length)" class="muted">{{ (r.state === 'succeeded' ? '发布已完成,可查看结果和恢复记录。' : '当前没有待提交动作,可查看历史或返回列表。') }}</p></section><section class="agent-panel"><h2>本次授权</h2><template data-if="(r.approval)"><template data-component="Tag" color="green">已授权修订 v{{ (r.approval.version) }}</template><p>{{ (r.approval.actor) }} · {{ (formatDate(r.approval.at)) }}</p><p class="review-text">{{ (r.approval.note) }}</p></template><p data-else="" class="muted">本修订版尚无有效人工授权。</p><p>目标:{{ (environmentLabel(v.environment)) }}<br>首次流量:{{ (v.traffic) }}%</p></section><section class="agent-panel"><h2>来源与记录</h2><template data-component="RouterLink" data-bind-to="('/tasks/' + row.task.id)">{{ (row.task.code) }} →</template><p><template data-component="RouterLink" data-if="(store.session.can('pbc:view', row.task.tenantId, r.pbcId))" data-bind-to="('/pbcs/' + r.pbcId + '/workspace')">{{ (row.project) }} →</template><span data-else="">{{ (row.project) }}</span></p><template data-component="Button" type="link" data-on-click="(router.push('/reviews?task=' + row.task.id))">查看质量确认</template><template data-component="RecordMetadata" data-bind-record="(r)"></template><p class="muted">记录版本 v{{ (r.recordVersion) }} · 逻辑删除:否</p></section><section data-if="(execution)" class="agent-panel release-demo"><h2>原型演示</h2><p class="muted">控制下一步模拟结果,不触发真实发布。</p><label for="release-event-outcome">下一步事件结果</label><template data-component="Select" id="release-event-outcome" data-model-value="(outcome)" data-bind-options="([{ value: 'pass', label: '成功' }, { value: 'fail', label: '步骤失败' }])"></template><label for="release-response">下一次提交响应</label><template data-component="Select" id="release-response" data-model-value="(store.response)" data-bind-options="([{ value: 'success', label: '正常返回' }, { value: 'failure', label: '提交失败(未执行)' }, { value: 'unknown', label: '响应不明(先查回执)' }])"></template></section></aside></div><template data-component="ReleaseEditor" data-if="(editing)" data-bind-row="(row)" data-on-close="(editing = false)" data-on-saved="editing = false;
|
|
3
|
+
selectedVersion = 0;
|
|
4
|
+
tab = 'evidence';"></template><template data-component="ReleaseActionDialog" data-if="(action)" data-bind-row="(row)" data-bind-action="(action)" data-bind-outcome="(action === 'advance' ? outcome : 'pass')" data-on-close="(action = undefined)" data-on-saved="(saved)"></template></template>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import OperationEntry from "../operations/OperationEntry.js";
|
|
2
|
+
import { computed, ref, watch } from '../../runtime/state.js';
|
|
3
|
+
import { useRoute, useRouter } from "../../runtime/router.js";
|
|
4
|
+
import { Alert, Button, Result, Space, Tag, Tabs, Select, Progress } from "../../runtime/widgets.js";
|
|
5
|
+
import { useReleases } from "./store.js";
|
|
6
|
+
import { versionOf, releaseLabels, releaseSummary, releaseColors, environmentLabel, releaseChecks, releaseUrl, actionLabels } from "./model.js";
|
|
7
|
+
import { formatDate } from "../shared/policy.js";
|
|
8
|
+
import RecordMetadata from "../shared/RecordMetadata.js";
|
|
9
|
+
import ReleaseEditor from "./ReleaseEditor.js";
|
|
10
|
+
import ReleaseActionDialog from "./ReleaseActionDialog.js";
|
|
11
|
+
import { createComponent } from '../../runtime/dom.js';
|
|
12
|
+
export default createComponent({ name: 'ReleaseDetail', propKeys: ["execution"], template: new URL('./ReleaseDetail.html', import.meta.url).href, setup(__props, __emit) {
|
|
13
|
+
const props = __props, route = useRoute(), router = useRouter(), store = useReleases(), row = computed(() => { const r = store.find(String(route.params.releaseId)); return r?.task.id === route.params.taskId ? r : undefined; }), r = computed(() => row.value?.release), v = computed(() => r.value ? versionOf(r.value) : undefined), tab = ref(String(route.query.tab || 'evidence')), editing = ref(false), action = ref(), outcome = ref('pass'), selectedVersion = ref(Number(route.query.version) || 0);
|
|
14
|
+
const evidence = computed(() => r.value?.versions.find(v => v.number === selectedVersion.value) || v.value), checks = computed(() => row.value ? releaseChecks(row.value.task, row.value.release) : []);
|
|
15
|
+
const actions = computed(() => { if (!r.value)
|
|
16
|
+
return []; if (r.value.unconfirmed)
|
|
17
|
+
return ['verify']; if (!props.execution)
|
|
18
|
+
return r.value.state === 'draft' || r.value.state === 'returned' ? ['check', 'submit'] : r.value.state === 'pending' ? ['approve', 'return'] : r.value.state === 'approved' ? ['start'] : []; return r.value.state === 'approved' ? ['start'] : ['deploying', 'canary', 'observing', 'rollingBack'].includes(r.value.state) ? ['advance', 'pause'] : r.value.state === 'paused' ? ['resume'] : r.value.state === 'failed' ? ['retry'] : []; });
|
|
19
|
+
const showRecovery = computed(() => props.execution && r.value?.startedAt && !['stopped', 'rolledBack'].includes(r.value.state));
|
|
20
|
+
function label(a) { if (a !== 'advance')
|
|
21
|
+
return actionLabels[a]; return r.value?.state === 'deploying' ? '部署完成,接入 ' + v.value?.traffic + '% 灰度' : r.value?.state === 'canary' ? '采集本次灰度观测' : r.value?.state === 'observing' ? '观测达标,继续全量' : '确认回滚结果'; }
|
|
22
|
+
const stepDone = (i) => !!r.value && (i === 0 ? !!r.value.approval : i === 1 || i === 2 ? r.value.receipts.some(x => x.action === 'advance' && ['canary', 'observing', 'succeeded'].includes(x.state)) : i === 3 ? r.value.receipts.some(x => x.state === 'succeeded') : ['succeeded', 'rolledBack', 'stopped'].includes(r.value.state));
|
|
23
|
+
const stages = ['人工授权', '部署制品', '首次灰度', '观测与全量', '发布结果'];
|
|
24
|
+
const effectiveState = computed(() => r.value?.state === 'failed' ? r.value.failedStage : r.value?.state === 'paused' ? r.value.previousState : r.value?.state);
|
|
25
|
+
const step = computed(() => !r.value ? 0 : ['draft', 'pending', 'returned'].includes(effectiveState.value || '') ? 0 : effectiveState.value === 'approved' ? 1 : effectiveState.value === 'deploying' ? 1 : effectiveState.value === 'canary' ? 2 : effectiveState.value === 'observing' ? 3 : ['succeeded', 'rolledBack', 'stopped'].includes(r.value.state) ? 4 : 2);
|
|
26
|
+
const saved = () => { const a = action.value; action.value = undefined; outcome.value = 'pass'; if (a === 'start' && r.value)
|
|
27
|
+
router.push(releaseUrl(r.value, true)); };
|
|
28
|
+
watch(() => [route.params.releaseId, route.query.tab, route.query.version], () => { selectedVersion.value = Number(route.query.version) || 0; action.value = undefined; editing.value = false; tab.value = String(route.query.tab || 'evidence'); });
|
|
29
|
+
return { OperationEntry, computed, ref, watch, useRoute, useRouter, Alert, Button, Result, Space, Tag, Tabs, Select, Progress, useReleases, versionOf, releaseLabels, releaseSummary, releaseColors, environmentLabel, releaseChecks, releaseUrl, actionLabels, formatDate, RecordMetadata, ReleaseEditor, ReleaseActionDialog, props, route, router, store, row, r, v, tab, editing, action, outcome, selectedVersion, evidence, checks, actions, showRecovery, label, stepDone, stages, effectiveState, step, saved };
|
|
30
|
+
} });
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<!-- 来源:src/releases/ReleaseEditor.vue;HTML / 原生 DOM 绑定,不含 Vue 运行时。 -->
|
|
2
|
+
<template data-component="Drawer" data-bind-open="(true)" data-bind-title="(row ? '修改发布单 · 生成新修订版' : '准备发布')" data-bind-size="(760)" data-bind-mask="({ closable: false })" data-on-close="(emit('close'))"><template data-component="Alert" data-if="(restored)" class="page-alert" type="info" show-icon="" title="已恢复本账号上次未提交的输入"></template><template data-component="Alert" data-if="(stale)" class="page-alert" type="warning" show-icon="" title="当前记录已更新,输入仍保留" description="请对照当前来源和发布详情,核对后再提交。"><template data-slot="action"><template data-component="Button" data-on-click="(rebase)">已核对,采用当前版本</template></template></template><template data-component="Alert" data-if="(error)" class="page-alert" type="error" show-icon="" data-bind-title="(error)"></template><template data-component="ProForm" name="release-editor" data-bind-form="(form)" layout="vertical" data-bind-disabled="(busy)"></template><div data-if="(task)" class="release-note"><strong>{{ (task.binding.project) }} · 规格 v{{ (task.binding.spec.number) }} · 流程 v{{ (task.binding.flow.number) }}</strong><p>{{ (task.releaseMaterials ? '此任务提供独立发布材料,保存后仍需检查和人工授权。' : '此任务没有独立发布材料,可保存草稿;缺失证据会阻止提交。') }}</p><p>{{ (task.releaseMaterials?.rollback ? '已有稳定版本 ' + task.releaseMaterials.rollback.version + ';回滚不自动还原数据库。' : '首次发布暂无稳定版本可回滚;异常时停止并撤流。') }}</p></div><p class="muted">关闭会保留本账号未提交的输入。保存草稿不产生部署或流量。</p><p data-if="(!allowed)" class="muted">当前账号缺少来源 PBC 或目标环境的发布编辑权限,请在权限管理中配置。</p><template data-slot="footer"><template data-component="Space" wrap=""><template data-component="Button" data-on-click="(emit('close'))">关闭并保留输入</template><template data-component="Button" type="primary" data-bind-disabled="(!allowed || stale)" data-bind-loading="(busy)" data-on-click="(save)">保存发布草稿</template></template></template></template>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { computed, ref, watch } from '../../runtime/state.js';
|
|
2
|
+
import { Alert, Button, Drawer, Space, message } from "../../runtime/widgets.js";
|
|
3
|
+
import { ProForm, useForm } from "../../runtime/widgets.js";
|
|
4
|
+
import { useReleases } from "./store.js";
|
|
5
|
+
import { environments, versionOf } from "./model.js";
|
|
6
|
+
import { clone } from "../tasks/domain.js";
|
|
7
|
+
import { createComponent } from '../../runtime/dom.js';
|
|
8
|
+
export default createComponent({ name: 'ReleaseEditor', propKeys: ["row", "taskId"], template: new URL('./ReleaseEditor.html', import.meta.url).href, setup(__props, __emit) {
|
|
9
|
+
const props = __props, emit = __emit, store = useReleases(), busy = ref(false), error = ref('');
|
|
10
|
+
const storage = 'i3-prototype-antdv-release-editor:' + store.session.actor.id + ':' + (props.row?.id || props.taskId || 'new');
|
|
11
|
+
const first = props.row?.task || store.tasks.find(t => t.id === props.taskId) || store.tasks.find(t => !store.sourceReason(t));
|
|
12
|
+
const baseline = { name: first?.name.replace('样例', '') || '', version: '1.0.0', environment: 'production', config: first?.releaseMaterials?.config || 'config-v1', description: '', traffic: 10, errorLimit: 1, latencyLimit: 800, rollbackPlan: '' };
|
|
13
|
+
const initial = { ...baseline, ...(props.row ? clone(versionOf(props.row.release)) : {}), taskId: first?.id || '' };
|
|
14
|
+
let saved;
|
|
15
|
+
try {
|
|
16
|
+
saved = JSON.parse(sessionStorage.getItem(storage) || 'null');
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
saved = null;
|
|
20
|
+
}
|
|
21
|
+
const expected = ref(saved?.expected ?? (props.row?.recordVersion || first?.recordVersion || 0)), request = ref(saved?.request || crypto.randomUUID()), restored = ref(!!saved);
|
|
22
|
+
const form = useForm({ ...initial, ...saved?.data }, [
|
|
23
|
+
{ path: 'taskId', label: '来源交付任务', component: 'select', span: 24, disabled: !!props.row, options: computed(() => store.tasks.map(t => ({ value: t.id, label: t.code + ' · ' + t.name + (store.sourceReason(t) ? ' · ' + store.sourceReason(t) : ''), disabled: !!store.sourceReason(t) }))), rules: [{ required: true, message: '请选择来源任务' }] },
|
|
24
|
+
{ path: 'name', label: '发布名称', component: 'input', span: 24, maxlength: 80, rules: [{ required: true, whitespace: true, message: '请输入发布名称' }] },
|
|
25
|
+
{ path: 'version', label: '应用版本', component: 'input', span: 12, xs: 24, sm: 12, placeholder: '例如 1.0.0', rules: [{ required: true, message: '请输入应用版本' }] },
|
|
26
|
+
{ path: 'environment', label: '目标环境', component: 'select', span: 12, xs: 24, sm: 12, options: environments, allowClear: false },
|
|
27
|
+
{ path: 'config', label: '配置快照', component: 'input', span: 24, extra: '与本次制品材料一并核对;修改配置后需重新检查。' },
|
|
28
|
+
{ path: 'description', label: '发布说明', component: 'textarea', span: 24, rows: 3, maxlength: 2000, placeholder: '本次范围、影响及验收关注点' },
|
|
29
|
+
{ path: 'traffic', label: '首次灰度比例(%)', component: 'input-number', span: 8, xs: 24, sm: 8, min: 1, max: 50 },
|
|
30
|
+
{ path: 'errorLimit', label: '错误率上限(%)', component: 'input-number', span: 8, xs: 24, sm: 8, min: 0.01, max: 100 },
|
|
31
|
+
{ path: 'latencyLimit', label: 'P95 延迟上限(ms)', component: 'input-number', span: 8, xs: 24, sm: 8, min: 1 },
|
|
32
|
+
{ path: 'rollbackPlan', label: '回退与恢复方案', component: 'textarea', span: 24, rows: 3, maxlength: 2000, placeholder: '回滚目标、停止撤流条件,以及数据库处理说明' },
|
|
33
|
+
]);
|
|
34
|
+
const task = computed(() => store.tasks.find(t => t.id === form.formData.taskId)), currentVersion = computed(() => props.row ? store.find(props.row.id)?.recordVersion : task.value?.recordVersion), stale = computed(() => expected.value !== currentVersion.value);
|
|
35
|
+
const allowed = computed(() => !!task.value && store.session.can('release:edit', task.value.tenantId, task.value.binding.pbcId, form.formData.environment));
|
|
36
|
+
watch(() => form.formData.taskId, () => { if (!props.row) {
|
|
37
|
+
expected.value = task.value?.recordVersion || 0;
|
|
38
|
+
request.value = crypto.randomUUID();
|
|
39
|
+
} });
|
|
40
|
+
watch([() => JSON.stringify(form.formData), expected, request], () => { try {
|
|
41
|
+
sessionStorage.setItem(storage, JSON.stringify({ data: form.formData, expected: expected.value, request: request.value }));
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
error.value = '输入缓存保存失败,请保留输入后重试。';
|
|
45
|
+
} }, { flush: 'sync' });
|
|
46
|
+
function rebase() { expected.value = currentVersion.value || 0; request.value = crypto.randomUUID(); error.value = ''; restored.value = false; }
|
|
47
|
+
async function save() { try {
|
|
48
|
+
await form.formRef.value?.validate();
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return;
|
|
52
|
+
} if (busy.value || !task.value)
|
|
53
|
+
return; busy.value = true; error.value = ''; try {
|
|
54
|
+
const f = form.formData, payload = { name: f.name, version: f.version, environment: f.environment, config: f.config, description: f.description, traffic: f.traffic, errorLimit: f.errorLimit, latencyLimit: f.latencyLimit, rollbackPlan: f.rollbackPlan };
|
|
55
|
+
const r = props.row ? store.apply(task.value.id, props.row.id, { id: request.value, expected: expected.value, action: 'edit', note: '修订发布单', input: payload }) : store.create(task.value.id, payload, request.value, expected.value);
|
|
56
|
+
sessionStorage.removeItem(storage);
|
|
57
|
+
message.success(r.unconfirmed ? '提交响应待核实,请查询原回执' : '发布草稿已保存');
|
|
58
|
+
emit('saved', r.id);
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
error.value = e.message;
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
busy.value = false;
|
|
65
|
+
} }
|
|
66
|
+
return { computed, ref, watch, Alert, Button, Drawer, Space, message, ProForm, useForm, useReleases, environments, versionOf, clone, props, emit, store, busy, error, storage, first, baseline, initial, saved, expected, request, restored, form, task, currentVersion, stale, allowed, rebase, save };
|
|
67
|
+
} });
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import ReleaseDetail from "./ReleaseDetail.js";
|
|
2
|
+
import { createComponent } from '../../runtime/dom.js';
|
|
3
|
+
export default createComponent({ name: 'ReleaseExecution', propKeys: [], template: new URL('./ReleaseExecution.html', import.meta.url).href, setup(__props, __emit) {
|
|
4
|
+
return { ReleaseDetail };
|
|
5
|
+
} });
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<!-- 来源:src/releases/ReleasesPage.vue;HTML / 原生 DOM 绑定,不含 Vue 运行时。 -->
|
|
2
|
+
<div class="page-heading"><div><h1>门禁与发布</h1><p>从交付产物准备发布,核对证据后人工放行,跟踪灰度与恢复结果。</p></div><template data-component="Button" type="primary" data-bind-disabled="(!store.session.can('release:edit'))" data-on-click="(creating = true)">准备发布</template></div><div class="task-metrics"><article><span>待准备 / 修订</span><strong>{{ (store.rows.filter(r => !r.release.unconfirmed && ['draft', 'returned'].includes(r.release.state)).length) }}</strong></article><article><span>待人工授权</span><strong>{{ (store.rows.filter(r => !r.release.unconfirmed && r.release.state === 'pending').length) }}</strong></article><article><span>执行 / 待处理</span><strong>{{ (store.rows.filter(r => r.release.unconfirmed || ['approved', 'deploying', 'canary', 'observing', 'failed', 'paused', 'rollingBack'].includes(r.release.state)).length) }}</strong></article><article><span>已结束</span><strong>{{ (store.rows.filter(r => !r.release.unconfirmed && ['succeeded', 'rolledBack', 'stopped'].includes(r.release.state)).length) }}</strong></article></div><template data-component="Alert" data-if="(route.query.task)" type="info" show-icon="" class="page-alert" title="正在查看来源任务的发布单"><template data-slot="action"><template data-component="Button" data-on-click="(router.replace('/releases'))">查看全部发布</template></template></template><template data-component="Alert" data-if="(!store.session.can('release:edit'))" type="info" show-icon="" class="page-alert" title="当前账号尚未配置发布操作权限" description="既有账号的授权保持原样。管理员可在权限管理中配置发布动作及租户、PBC、目标环境范围。"></template><section class="auth-list"><div class="auth-filters"><template data-component="Input" data-model-value="(query)" aria-label="搜索发布单" placeholder="搜索发布名称、编号或任务" allow-clear=""></template><template data-component="Select" data-model-value="(state)" aria-label="发布状态筛选" data-bind-options="([{ value: 'all', label: '全部状态' }, ...Object.entries(releaseLabels).map(([value, label]) => ({ value, label }))])"></template><template data-component="Select" data-model-value="(env)" aria-label="发布环境筛选" data-bind-options="([{ value: 'all', label: '全部环境' }, ...environments])"></template><template data-component="Select" data-model-value="(project)" aria-label="发布 PBC 筛选" data-bind-options="([{ value: 'all', label: '全部可见 PBC' }, ...Array.from(new Map(store.rows.map(r => [r.task.binding.pbcId, { value: r.task.binding.pbcId, label: r.project }])).values())])"></template><template data-component="Button" data-on-click="(reset)">重置</template></div><div class="auth-tools"><template data-component="ListBatchBar" data-bind-count="(selection.selected.value.length)" data-bind-allowed="(store.session.can('release:delete'))" data-on-remove="(selectRows(selection.selected.value))" data-on-clear="(selection.clear)"></template><template data-component="Space" wrap=""><template data-component="Select" data-model-value="(sort)" aria-label="发布排序" data-bind-options="([{ value: 'newest', label: '更新时间倒序' }, { value: 'oldest', label: '更新时间正序' }])"></template><template data-component="Button" data-bind-loading="(loading)" data-on-click="(refresh)">刷新</template><template data-component="ColumnSettings" data-bind-preferences="(prefs)"></template></template></div><template data-component="Alert" data-if="(error || store.notice)" type="error" show-icon="" data-bind-title="(error || store.notice)"></template><template data-component="Alert" data-if="(store.scene === 'error')" type="error" show-icon="" title="发布列表加载失败" description="已有记录保留,重试不会创建新发布单。"><template data-slot="action"><template data-component="Button" data-on-click="(refresh)">重试</template></template></template><template data-component="ProTable" data-else="" data-bind-table="(table)" data-bind-control="(false)" data-bind-table-container="(false)" data-bind-search="(async () => { })" row-key="id" data-bind-row-selection="(selection.rowSelection.value)" data-bind-loading="(loading)" data-bind-scroll="({ x: 'max-content' })" data-bind-pagination="({ showTotal: (n) => '共 ' + n + ' 项', showSizeChanger: true, pageSizeOptions: [5, 10, 20] })"></template><div data-if="(!filtered.length && store.scene === 'normal')" class="release-empty"><p>{{ (store.rows.length ? '当前筛选没有发布单,可重置筛选。' : '还没有发布单。从已完成质量检查的任务开始准备。') }}</p><template data-component="Button" data-if="(store.rows.length)" data-on-click="(reset)">重置筛选</template><template data-component="Button" data-else="" data-on-click="(addExamples)">载入发布演示样例</template><p class="muted">样例使用独立模拟材料,不补写已有任务的缺失证据。</p></div></section><p class="review-footnote">只允许删除未提交的发布草稿;授权、执行、失败和恢复记录须保留。材料检查与灰度观测为两个阶段,首次生产流量前必须人工授权。</p><template data-component="ReleaseEditor" data-if="(creating || editing)" data-bind-row="(editing)" data-bind-task-id="(String(route.query.task || ''))" data-on-close="creating = false;
|
|
3
|
+
editing = undefined;" data-on-saved="(saved)"></template><template data-component="ListDeleteDialog" data-if="(targets.length)" data-model-targets="(targets)" label="发布草稿" data-bind-check="(store.checkDelete)" data-bind-submit="(store.removeMany)" data-on-close="(targets = [])" data-on-deleted="targets = [];
|
|
4
|
+
selection.clear();"></template>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { computed, ref, watch } from '../../runtime/state.js';
|
|
2
|
+
import { h } from '../../runtime/dom.js';
|
|
3
|
+
import { useRoute, useRouter } from "../../runtime/router.js";
|
|
4
|
+
import { Alert, Button, Input, Select, Space, Tag, Empty, message } from "../../runtime/widgets.js";
|
|
5
|
+
import { ProTable, useTable } from "../../runtime/widgets.js";
|
|
6
|
+
import { useReleases } from "./store.js";
|
|
7
|
+
import { releaseLabels, releaseColors, versionOf, environments, environmentLabel, releaseUrl } from "./model.js";
|
|
8
|
+
import { formatDate, tenantLabel } from "../shared/policy.js";
|
|
9
|
+
import { useColumns } from "../shared/columns.js";
|
|
10
|
+
import { useListSelection } from "../shared/listSelection.js";
|
|
11
|
+
import ListMore from "../shared/ListMore.js";
|
|
12
|
+
import ListBatchBar from "../shared/ListBatchBar.js";
|
|
13
|
+
import ListDeleteDialog from "../shared/ListDeleteDialog.js";
|
|
14
|
+
import ColumnSettings from "../shared/ColumnSettings.js";
|
|
15
|
+
import ReleaseEditor from "./ReleaseEditor.js";
|
|
16
|
+
import { createComponent } from '../../runtime/dom.js';
|
|
17
|
+
export default createComponent({ name: 'ReleasesPage', propKeys: [], template: new URL('./ReleasesPage.html', import.meta.url).href, setup(__props, __emit) {
|
|
18
|
+
const store = useReleases(), route = useRoute(), router = useRouter(), query = ref(''), state = ref('all'), env = ref('all'), project = ref(String(route.query.pbc || 'all')), sort = ref('newest'), loading = ref(false), editing = ref(), creating = ref(route.query.prepare === '1'), targets = ref([]), error = ref('');
|
|
19
|
+
const prefs = useColumns('releases-v013', [{ key: 'name', label: '发布单', locked: true }, { key: 'project', label: '所属 PBC', defaultVisible: true }, { key: 'environment', label: '目标环境', defaultVisible: true }, { key: 'state', label: '发布状态', defaultVisible: true }, { key: 'createdBy', label: '创建人', defaultVisible: true }, { key: 'createdAt', label: '创建时间', defaultVisible: true }, { key: 'updatedBy', label: '更新人' }, { key: 'updatedAt', label: '更新时间' }, { key: 'tenant', label: '租户' }, { key: 'department', label: '创建部门' }, { key: 'approver', label: '授权人' }, { key: 'approvedAt', label: '授权时间' }, { key: 'version', label: '记录版本' }, { key: 'actions', label: '操作', locked: true }]);
|
|
20
|
+
function more(r) { return [{ key: 'edit', label: '修改发布单', disabled: !!store.reason(r, 'edit') }, { key: 'execution', label: '执行与结果' }, { key: 'task', label: '来源任务' }, { key: 'history', label: '版本与操作记录' }, { key: 'delete', label: '删除草稿', danger: true, disabled: !!store.reason(r, 'delete') }]; }
|
|
21
|
+
function selectRows(rows) { targets.value = rows.map(r => ({ id: r.id, name: versionOf(r.release).name, recordVersion: r.recordVersion })); }
|
|
22
|
+
function action(key, r) { if (key === 'edit') {
|
|
23
|
+
editing.value = r;
|
|
24
|
+
return;
|
|
25
|
+
} if (key === 'delete') {
|
|
26
|
+
selectRows([r]);
|
|
27
|
+
return;
|
|
28
|
+
} router.push(key === 'task' ? '/tasks/' + r.task.id : releaseUrl(r.release, key === 'execution') + (key === 'history' ? '?tab=history' : '')); }
|
|
29
|
+
const render = (f) => (_, row) => f(row);
|
|
30
|
+
const columns = prefs.choices.map(c => ({ key: c.key, title: c.label, width: c.key === 'name' ? 285 : c.key === 'actions' ? 145 : c.key.endsWith('At') ? 165 : 130, fixed: c.key === 'actions' ? 'right' : undefined, render: render(r => { const x = r.release, v = versionOf(x); if (c.key === 'name')
|
|
31
|
+
return h('div', { class: 'review-list-name' }, [h(Button, { type: 'link', onClick: () => router.push(releaseUrl(x)) }, () => v.name), h('small', { class: 'mono muted' }, x.code + ' · ' + v.version + ' / 修订 v' + v.number), h('span', { class: 'muted' }, x.unconfirmed ? '提交结果待核实' : r.task.code)]); if (c.key === 'actions')
|
|
32
|
+
return h('div', { class: 'row-actions' }, [h(Button, { type: 'link', onClick: () => router.push(releaseUrl(x)) }, () => ['draft', 'returned', 'pending', 'approved'].includes(x.state) ? '处理' : '查看'), h(ListMore, { name: x.code, items: more(r), onAction: (key) => action(key, r) })]); if (c.key === 'state')
|
|
33
|
+
return h(Tag, { color: releaseColors[x.state] }, () => x.unconfirmed ? '结果待核实' : releaseLabels[x.state]); if (c.key === 'environment')
|
|
34
|
+
return environmentLabel(v.environment); if (c.key === 'project')
|
|
35
|
+
return r.project; if (c.key === 'tenant')
|
|
36
|
+
return tenantLabel(x.tenantId); if (c.key === 'department')
|
|
37
|
+
return x.createdDept; if (c.key === 'approver')
|
|
38
|
+
return x.approval?.actor || '—'; if (c.key === 'approvedAt')
|
|
39
|
+
return formatDate(x.approval?.at || ''); if (c.key === 'version')
|
|
40
|
+
return 'v' + x.recordVersion; if (c.key.endsWith('At'))
|
|
41
|
+
return formatDate(x[c.key]); return x[c.key]; }) }));
|
|
42
|
+
const table = useTable({ columns, pageParam: { current: 1, pageSize: 5, total: 0 } }), allColumns = new Map(table.columns.value.map(c => [c.key, c]));
|
|
43
|
+
watch(prefs.keys, keys => table.columns.value = keys.map(k => allColumns.get(k)).filter(Boolean), { immediate: true });
|
|
44
|
+
const filtered = computed(() => store.scene === 'empty' ? [] : store.rows.filter(r => (!route.query.task || r.task.id === route.query.task) && (state.value === 'all' || r.release.state === state.value) && (env.value === 'all' || versionOf(r.release).environment === env.value) && (project.value === 'all' || r.task.binding.pbcId === project.value) && (versionOf(r.release).name + r.release.code + r.task.code).toLowerCase().includes(query.value.trim().toLowerCase())).sort((a, b) => (sort.value === 'newest' ? -1 : 1) * a.release.updatedAt.localeCompare(b.release.updatedAt)));
|
|
45
|
+
watch([query, state, env, project, sort], () => table.setPageParam({ current: 1 }));
|
|
46
|
+
watch([filtered, () => table.pageParam.current, () => table.pageParam.pageSize], () => { const size = table.pageParam.pageSize || 5; table.pageParam.current = Math.min(table.pageParam.current || 1, Math.max(1, Math.ceil(filtered.value.length / size))); table.pageParam.total = filtered.value.length; table.dataSource.value = filtered.value.slice(((table.pageParam.current || 1) - 1) * size, (table.pageParam.current || 1) * size); }, { immediate: true });
|
|
47
|
+
const selection = useListSelection(table.dataSource, computed(() => JSON.stringify([store.session.key, query.value, state.value, env.value, project.value, sort.value, table.pageParam.current, table.pageParam.pageSize])), computed(() => true), r => r.release.code);
|
|
48
|
+
async function refresh() { selection.clear(); loading.value = true; await new Promise(r => setTimeout(r, 180)); store.scene = 'normal'; loading.value = false; }
|
|
49
|
+
function reset() { query.value = ''; state.value = 'all'; env.value = 'all'; project.value = 'all'; }
|
|
50
|
+
function addExamples() { try {
|
|
51
|
+
const n = store.addExamples();
|
|
52
|
+
message.success(n ? '已添加独立发布样例' : '发布样例已存在,请选择对应任务');
|
|
53
|
+
creating.value = true;
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
error.value = e.message;
|
|
57
|
+
} }
|
|
58
|
+
function saved(id) { creating.value = false; editing.value = undefined; router.push(releaseUrl(store.find(id).release)); }
|
|
59
|
+
return { computed, ref, watch, h, useRoute, useRouter, Alert, Button, Input, Select, Space, Tag, Empty, message, ProTable, useTable, useReleases, releaseLabels, releaseColors, versionOf, environments, environmentLabel, releaseUrl, formatDate, tenantLabel, useColumns, useListSelection, ListMore, ListBatchBar, ListDeleteDialog, ColumnSettings, ReleaseEditor, store, route, router, query, state, env, project, sort, loading, editing, creating, targets, error, prefs, more, selectRows, action, render, columns, table, allColumns, filtered, selection, refresh, reset, addExamples, saved };
|
|
60
|
+
} });
|