@synopackageland/cli 0.6.1 → 0.7.0
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/CHANGELOG.md +613 -0
- package/README.md +81 -89
- package/dist/add.js +39 -16
- package/dist/build-spk/delegate-scope.d.ts +6 -0
- package/dist/build-spk/delegate-scope.js +34 -0
- package/dist/build-spk/dsm-arch.d.ts +7 -1
- package/dist/build-spk/dsm-arch.js +24 -28
- package/dist/build-spk/dsm-platforms.json +39 -0
- package/dist/build-spk/dsm-version.d.ts +11 -0
- package/dist/build-spk/dsm-version.js +17 -0
- package/dist/build-spk/gateway.d.ts +1 -1
- package/dist/build-spk/gateway.js +2 -2
- package/dist/build-spk/generator.js +30 -5
- package/dist/build-spk/notices.js +4 -1
- package/dist/build-spk/resource-worker.js +3 -0
- package/dist/build-spk/wizard-messages.d.ts +66 -0
- package/dist/build-spk/wizard-messages.js +75 -9
- package/dist/cli.js +15 -7
- package/dist/contract/schema-validate.js +8 -0
- package/dist/contract/validate.js +30 -0
- package/dist/deploy/deploy.d.ts +1 -1
- package/dist/deploy/deploy.js +8 -0
- package/dist/deploy/plan.js +1 -1
- package/dist/deploy/spk-version.js +1 -1
- package/dist/error-registry.js +43 -15
- package/dist/host/dsm-client.d.ts +8 -4
- package/dist/host/dsm-client.js +10 -4
- package/dist/host/types.d.ts +2 -1
- package/dist/info.js +18 -2
- package/dist/init.d.ts +3 -0
- package/dist/init.js +49 -18
- package/dist/package-metadata.d.ts +21 -0
- package/dist/package-metadata.js +101 -0
- package/dist/portal/alias.d.ts +25 -0
- package/dist/portal/alias.js +48 -0
- package/dist/portal/package.d.ts +33 -0
- package/dist/portal/package.js +116 -0
- package/dist/runtime/delegate-contract.d.ts +79 -0
- package/dist/runtime/delegate-contract.js +27 -0
- package/dist/runtime/executor-build.d.ts +1 -1
- package/dist/runtime/executor-build.js +5 -4
- package/dist/runtime/executor-inspect.d.ts +3 -0
- package/dist/runtime/executor-inspect.js +11 -5
- package/dist/runtime/executor-policy.d.ts +12 -0
- package/dist/runtime/executor-policy.js +43 -3
- package/dist/runtime/executor-scope-task.d.ts +4 -0
- package/dist/runtime/executor-scope-task.js +21 -0
- package/dist/runtime/executor-tasks.d.ts +2 -1
- package/dist/runtime/executor-tasks.js +4 -4
- package/dist/skill.js +1 -0
- package/dist/standalone/api-permissions.d.ts +10 -0
- package/dist/standalone/api-permissions.js +19 -2
- package/dist/standalone/background.d.ts +16 -0
- package/dist/standalone/background.js +69 -2
- package/dist/standalone/binary.d.ts +1 -1
- package/dist/standalone/binary.js +22 -8
- package/dist/standalone/build.d.ts +1 -1
- package/dist/standalone/build.js +38 -8
- package/dist/standalone/daemon.d.ts +1 -1
- package/dist/standalone/daemon.js +2 -1
- package/dist/standalone/dsm-window.d.ts +3 -1
- package/dist/standalone/dsm-window.js +72 -15
- package/dist/standalone/inspect-spk.d.ts +8 -0
- package/dist/standalone/inspect-spk.js +105 -26
- package/dist/standalone/lifecycle.js +86 -8
- package/dist/standalone/manifest.d.ts +13 -0
- package/dist/standalone/manifest.js +57 -8
- package/dist/standalone/native-authorization-window.d.ts +1 -1
- package/dist/standalone/native-authorization-window.js +2 -0
- package/dist/standalone/native-layout.d.ts +3 -0
- package/dist/standalone/native-layout.js +37 -5
- package/dist/standalone/portal-window.d.ts +21 -0
- package/dist/standalone/portal-window.js +155 -0
- package/dist/standalone/portal.d.ts +4 -0
- package/dist/standalone/portal.js +4 -0
- package/dist/standalone/resource.d.ts +5 -3
- package/dist/standalone/resource.js +6 -3
- package/dist/standalone/root-window.js +62 -15
- package/dist/standalone/spk.d.ts +1 -1
- package/dist/standalone/spk.js +25 -7
- package/dist/templates/_daemon/main_test.go +61 -8
- package/dist/templates/_executor/README.md +127 -16
- package/dist/templates/_executor/anchor.go +146 -0
- package/dist/templates/_executor/anchor_test.go +126 -0
- package/dist/templates/_executor/apppriv_review_test.go +1 -1
- package/dist/templates/_executor/cancel_contract_test.go +236 -0
- package/dist/templates/_executor/copy.go +64 -0
- package/dist/templates/_executor/copy_test.go +88 -0
- package/dist/templates/_executor/dsm.go +23 -10
- package/dist/templates/_executor/dsm_test.go +95 -1
- package/dist/templates/_executor/executor.go +260 -49
- package/dist/templates/_executor/executor_test.go +36 -6
- package/dist/templates/_executor/identity.go +27 -1
- package/dist/templates/_executor/identity_kernel_linux_test.go +60 -0
- package/dist/templates/_executor/identity_linux.go +82 -0
- package/dist/templates/_executor/identity_security_test.go +43 -0
- package/dist/templates/_executor/internal/lifecycle/lifecycle.go +5 -0
- package/dist/templates/_executor/internal/lifecycle/lifecycle_test.go +61 -3
- package/dist/templates/_executor/internal/taskscript/boot.sh +9 -9
- package/dist/templates/_executor/internal/taskscript/cmd/main.go +2 -2
- package/dist/templates/_executor/internal/taskscript/control.sh +15 -10
- package/dist/templates/_executor/internal/taskscript/tasks.go +5 -3
- package/dist/templates/_executor/lifecycle_test.go +11 -0
- package/dist/templates/_executor/link_linux.go +28 -0
- package/dist/templates/_executor/long_running_test.go +530 -0
- package/dist/templates/_executor/main.go +72 -20
- package/dist/templates/_executor/move_linux.go +490 -0
- package/dist/templates/_executor/move_linux_test.go +460 -0
- package/dist/templates/_executor/move_validation.go +46 -0
- package/dist/templates/_executor/move_validation_test.go +187 -0
- package/dist/templates/_executor/operations.go +926 -0
- package/dist/templates/_executor/platform_linux.go +24 -173
- package/dist/templates/_executor/platform_linux_test.go +9 -65
- package/dist/templates/_executor/platform_other.go +30 -4
- package/dist/templates/_executor/process.go +3 -0
- package/dist/templates/_executor/records.go +242 -0
- package/dist/templates/_executor/scope_cancel_contract_test.go +91 -0
- package/dist/templates/_executor/scope_client.go +37 -0
- package/dist/templates/_executor/scope_contract.go +12 -0
- package/dist/templates/_executor/selected_scope.go +146 -0
- package/dist/templates/_executor/selected_scope_test.go +186 -0
- package/dist/templates/_executor/status_disabled_test.go +33 -0
- package/dist/templates/_executor/store.go +4 -3
- package/dist/templates/_executor/supervise.go +109 -0
- package/dist/templates/_executor/supervise_test.go +130 -0
- package/dist/templates/_executor/task_race_review_test.go +1 -1
- package/dist/templates/_executor/tasks.go +7 -8
- package/dist/templates/_executor/tasks_review_test.go +1 -1
- package/dist/templates/_executor/tasks_test.go +4 -4
- package/dist/templates/_executor/watch.go +8 -0
- package/dist/templates/_executor/watch_test.go +23 -2
- package/dist/templates/_executor/write_review_linux_test.go +4 -12
- package/dist/templates/_shared/AGENTS.md +129 -0
- package/dist/templates/binary/dsm-package.yaml +24 -0
- package/dist/templates/binary/i18n/messages/de.json +4 -0
- package/dist/templates/binary/i18n/messages/en.json +4 -0
- package/dist/templates/binary/i18n/messages/es.json +4 -0
- package/dist/templates/binary/i18n/messages/fr.json +4 -0
- package/dist/templates/binary/i18n/messages/it.json +4 -0
- package/dist/templates/binary/i18n/messages/ja.json +4 -0
- package/dist/templates/binary/i18n/messages/ko.json +4 -0
- package/dist/templates/binary/i18n/messages/zh-CN.json +4 -0
- package/dist/templates/binary/i18n/messages/zh-TW.json +4 -0
- package/dist/templates/binary/ui/app.js +19 -4
- package/dist/templates/native-authorization/AuthorizationGate.vue +5 -3
- package/dist/templates/native-authorization/i18n/de.json +1 -0
- package/dist/templates/native-authorization/i18n/en.json +1 -0
- package/dist/templates/native-authorization/i18n/es.json +2 -1
- package/dist/templates/native-authorization/i18n/fr.json +1 -0
- package/dist/templates/native-authorization/i18n/it.json +11 -10
- package/dist/templates/native-authorization/i18n/ja.json +1 -0
- package/dist/templates/native-authorization/i18n/ko.json +10 -9
- package/dist/templates/native-authorization/i18n/zh-CN.json +6 -5
- package/dist/templates/native-authorization/i18n/zh-TW.json +6 -5
- package/dist/templates/native-go/README.md +43 -10
- package/dist/templates/native-go/backend/cmd/app/main.go +27 -7
- package/dist/templates/native-go/backend/internal/api/entry_test.go +47 -0
- package/dist/templates/native-go/backend/internal/api/permissions_test.go +3 -3
- package/dist/templates/native-go/backend/internal/api/registry.go +11 -0
- package/dist/templates/native-go/backend/internal/api/sdk-permissions.json +14 -1
- package/dist/templates/native-go/backend/internal/api/server.go +18 -5
- package/dist/templates/native-go/backend/internal/app/delegate_config.go +57 -0
- package/dist/templates/native-go/backend/internal/app/delegate_config_test.go +28 -0
- package/dist/templates/native-go/backend/internal/app/portal.go +30 -0
- package/dist/templates/native-go/backend/internal/app/portal_test.go +55 -0
- package/dist/templates/native-go/backend/internal/app/routes.go +8 -2
- package/dist/templates/native-go/backend/internal/background/runner.go +1 -1
- package/dist/templates/native-go/backend/internal/background/store.go +16 -14
- package/dist/templates/native-go/backend/internal/dsm/admin.go +8 -2
- package/dist/templates/native-go/backend/internal/dsm/admin_linux.go +43 -13
- package/dist/templates/native-go/backend/internal/dsm/admin_linux_test.go +56 -0
- package/dist/templates/native-go/backend/internal/dsm/admin_other.go +4 -1
- package/dist/templates/native-go/backend/internal/dsm/apppriv.go +30 -0
- package/dist/templates/native-go/backend/internal/dsm/bridge_file.go +75 -0
- package/dist/templates/native-go/backend/internal/dsm/bridge_file_test.go +57 -0
- package/dist/templates/native-go/backend/internal/dsm/entry.go +177 -0
- package/dist/templates/native-go/backend/internal/dsm/entry_test.go +315 -0
- package/dist/templates/native-go/backend/internal/dsm/native/admin-armv7.elf +0 -0
- package/dist/templates/native-go/backend/internal/dsm/native/apppriv-aarch64.elf +0 -0
- package/dist/templates/native-go/backend/internal/dsm/native/apppriv-armv7.elf +0 -0
- package/dist/templates/native-go/backend/internal/dsm/native/apppriv-x86_64.elf +0 -0
- package/dist/templates/native-go/backend/internal/dsm/native/apppriv_check.c +33 -0
- package/dist/templates/native-go/backend/internal/dsm/native/build.sh +13 -4
- package/dist/templates/native-go/backend/internal/dsm/native/provenance.json +9 -3
- package/dist/templates/native-go/backend/internal/filestation/check_permission.go +71 -0
- package/dist/templates/native-go/backend/internal/filestation/check_permission_test.go +77 -0
- package/dist/templates/native-go/backend/internal/filestation/sdk_policy_test.go +39 -0
- package/dist/templates/native-go/backend/internal/filestation/service.go +1 -1
- package/dist/templates/native-go/backend/internal/portal/portal.go +138 -0
- package/dist/templates/native-go/backend/internal/portal/portal_test.go +89 -0
- package/dist/templates/native-go/dsm-package.yaml +20 -1
- package/dist/templates/native-go/i18n/glossary.json +2 -1
- package/dist/templates/native-go/i18n/messages/de.json +1 -0
- package/dist/templates/native-go/i18n/messages/en.json +1 -0
- package/dist/templates/native-go/i18n/messages/es.json +1 -0
- package/dist/templates/native-go/i18n/messages/fr.json +1 -0
- package/dist/templates/native-go/i18n/messages/it.json +1 -0
- package/dist/templates/native-go/i18n/messages/ja.json +1 -0
- package/dist/templates/native-go/i18n/messages/ko.json +1 -0
- package/dist/templates/native-go/i18n/messages/zh-CN.json +1 -0
- package/dist/templates/native-go/i18n/messages/zh-TW.json +1 -0
- package/dist/templates/native-go/ui/app.css +10 -0
- package/dist/templates/native-go/ui/app.js +11 -1
- package/dist/templates/native-go-delegate/DELEGATE.md +126 -5
- package/dist/templates/native-go-delegate/backend/cmd/background-worker/main.go +7 -0
- package/dist/templates/native-go-delegate/backend/internal/app/delegate.go +11 -1
- package/dist/templates/native-go-delegate/backend/internal/app/delegate_routes_test.go +14 -0
- package/dist/templates/native-go-delegate/backend/internal/background/delegated_moves.go +270 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/admission_recovery_test.go +410 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/cancel_contract_test.go +95 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/client.go +8 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/destination.go +110 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/destination_state.go +117 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/destination_state_test.go +60 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/destination_test.go +116 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/example.go +81 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/example_test.go +91 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/executor_contract_test.go +511 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/files.go +1 -1
- package/dist/templates/native-go-delegate/backend/internal/delegate/long_running_routes.go +92 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/long_running_routes_test.go +99 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/move.go +348 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/move_test.go +351 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/operations_list.go +65 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/operations_list_test.go +114 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/retry_test.go +1 -1
- package/dist/templates/native-go-delegate/backend/internal/delegate/service.go +58 -17
- package/dist/templates/native-go-delegate/backend/internal/delegate/staging.go +121 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/status_disabled_test.go +51 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/testdata/executor_contract.txt +112 -0
- package/dist/templates/native-go-delegate/backend/internal/delegate/transient_contract_test.go +137 -0
- package/dist/templates/native-go-delegate/backend/internal/filestation/files.go +1 -31
- package/dist/templates/native-go-delegate/frontend/src/CapabilityPanels.vue +30 -3
- package/dist/templates/native-go-delegate/frontend/src/delegate/BackgroundSavePanel.vue +113 -0
- package/dist/templates/native-go-delegate/frontend/src/delegate/copy.ts +5 -6
- package/dist/templates/native-go-delegate/frontend/test/background-save.test.ts +121 -0
- package/dist/templates/native-go-delegate/frontend/test/scope-settings.test.ts +49 -0
- package/dist/templates/native-go-delegate/test.sh +1 -0
- package/dist/templates/native-go-jobs/frontend/src/background/i18n/de.json +2 -2
- package/dist/templates/native-go-jobs/frontend/src/background/i18n/en.json +2 -2
- package/dist/templates/native-go-jobs/frontend/src/background/i18n/es.json +12 -12
- package/dist/templates/native-go-jobs/frontend/src/background/i18n/fr.json +4 -4
- package/dist/templates/native-go-jobs/frontend/src/background/i18n/it.json +17 -17
- package/dist/templates/native-go-jobs/frontend/src/background/i18n/ja.json +7 -7
- package/dist/templates/native-go-jobs/frontend/src/background/i18n/ko.json +15 -15
- package/dist/templates/native-go-jobs/frontend/src/background/i18n/zh-CN.json +3 -3
- package/dist/templates/native-go-jobs/frontend/src/background/i18n/zh-TW.json +2 -2
- package/dist/templates/native-go-jobs/frontend/test/background.test.ts +25 -28
- package/dist/templates/native-go-vue/README.md +4 -0
- package/dist/templates/native-go-vue/frontend/package-lock.json +4 -4
- package/dist/templates/native-go-vue/frontend/package.json +1 -1
- package/dist/templates/native-go-vue/frontend/src/App.vue +18 -13
- package/dist/templates/native-go-vue/frontend/src/context.ts +31 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/ExecutorSettings.vue +52 -8
- package/dist/templates/native-go-vue/frontend/src/delegate/catalogs.ts +11 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/copy.test.ts +12 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/copy.ts +4 -4
- package/dist/templates/native-go-vue/frontend/src/delegate/executor-setup.ts +4 -2
- package/dist/templates/native-go-vue/frontend/src/delegate/facade.test.ts +100 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/facade.ts +85 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/de.json +46 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/en.json +12 -2
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/es.json +46 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +46 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/it.json +46 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +46 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +46 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/de.json +42 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/en.json +42 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/es.json +42 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/fr.json +42 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/it.json +42 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/ja.json +42 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/ko.json +42 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-CN.json +42 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-TW.json +42 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/zh-CN.json +46 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +15 -5
- package/dist/templates/native-go-vue/frontend/src/delegate/long-running.ts +57 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/save-copy.test.ts +12 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/save-copy.ts +24 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/scoped-picker.test.ts +12 -0
- package/dist/templates/native-go-vue/frontend/src/delegate/scoped-picker.ts +21 -0
- package/dist/templates/native-go-vue/frontend/src/files/files.test.ts +7 -0
- package/dist/templates/native-go-vue/frontend/src/i18n/de.json +20 -0
- package/dist/templates/native-go-vue/frontend/src/i18n/en.json +20 -0
- package/dist/templates/native-go-vue/frontend/src/i18n/es.json +20 -0
- package/dist/templates/native-go-vue/frontend/src/i18n/fr.json +20 -0
- package/dist/templates/native-go-vue/frontend/src/i18n/it.json +20 -0
- package/dist/templates/native-go-vue/frontend/src/i18n/ja.json +20 -0
- package/dist/templates/native-go-vue/frontend/src/i18n/ko.json +20 -0
- package/dist/templates/native-go-vue/frontend/src/i18n/zh-CN.json +20 -0
- package/dist/templates/native-go-vue/frontend/src/i18n/zh-TW.json +20 -0
- package/dist/templates/native-go-vue/frontend/src/i18n.ts +13 -0
- package/dist/templates/native-go-vue/frontend/src/identity/LoginPage.vue +16 -14
- package/dist/templates/native-go-vue/frontend/src/identity/i18n/de.json +27 -0
- package/dist/templates/native-go-vue/frontend/src/identity/i18n/en.json +27 -0
- package/dist/templates/native-go-vue/frontend/src/identity/i18n/es.json +27 -0
- package/dist/templates/native-go-vue/frontend/src/identity/i18n/fr.json +27 -0
- package/dist/templates/native-go-vue/frontend/src/identity/i18n/it.json +27 -0
- package/dist/templates/native-go-vue/frontend/src/identity/i18n/ja.json +27 -0
- package/dist/templates/native-go-vue/frontend/src/identity/i18n/ko.json +27 -0
- package/dist/templates/native-go-vue/frontend/src/identity/i18n/zh-CN.json +27 -0
- package/dist/templates/native-go-vue/frontend/src/identity/i18n/zh-TW.json +27 -0
- package/dist/templates/native-go-vue/frontend/src/identity/i18n.ts +14 -0
- package/dist/templates/native-go-vue/frontend/src/identity/session.test.ts +4 -0
- package/dist/templates/native-go-vue/frontend/src/identity/session.ts +9 -8
- package/dist/templates/native-go-vue/frontend/src/nas/NasPicker.vue +31 -12
- package/dist/templates/native-go-vue/frontend/src/nas/destination.test.ts +48 -0
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/de.json +1 -1
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/en.json +1 -1
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/es.json +10 -10
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/fr.json +1 -1
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/it.json +12 -12
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/ja.json +15 -15
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/ko.json +12 -12
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/zh-CN.json +1 -1
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/zh-TW.json +1 -1
- package/dist/templates/native-go-vue/frontend/src/portal/PortalSettings.vue +39 -0
- package/dist/templates/native-go-vue/frontend/src/shell/i18n/fr.json +1 -1
- package/dist/templates/native-go-vue/frontend/src/shell/i18n/ja.json +1 -1
- package/dist/templates/native-go-vue/frontend/test/apps.test.ts +14 -0
- package/dist/templates/native-go-vue/frontend/test/portal-settings.test.ts +71 -0
- package/dist/templates/native-go-vue/frontend/test/shell-i18n.test.ts +50 -0
- package/package.json +5 -1
- package/schema/package-metadata.schema.json +101 -0
- package/schema/synology-app.schema.json +112 -0
- package/skills/SKILL.md +106 -4
- package/skills/reference/create.md +101 -2
- package/skills/reference/deploy.md +2 -2
- package/skills/reference/files.md +113 -2
- package/skills/reference/identity.md +70 -1
- package/skills/reference/portal.md +17 -0
- package/skills/reference/ui-vue.md +12 -0
- package/templates/_daemon/main_test.go +61 -8
- package/templates/_executor/README.md +127 -16
- package/templates/_executor/anchor.go +146 -0
- package/templates/_executor/anchor_test.go +126 -0
- package/templates/_executor/apppriv_review_test.go +1 -1
- package/templates/_executor/cancel_contract_test.go +236 -0
- package/templates/_executor/copy.go +64 -0
- package/templates/_executor/copy_test.go +88 -0
- package/templates/_executor/dsm.go +23 -10
- package/templates/_executor/dsm_test.go +95 -1
- package/templates/_executor/executor.go +260 -49
- package/templates/_executor/executor_test.go +36 -6
- package/templates/_executor/identity.go +27 -1
- package/templates/_executor/identity_kernel_linux_test.go +60 -0
- package/templates/_executor/identity_linux.go +82 -0
- package/templates/_executor/identity_security_test.go +43 -0
- package/templates/_executor/internal/lifecycle/lifecycle.go +5 -0
- package/templates/_executor/internal/lifecycle/lifecycle_test.go +61 -3
- package/templates/_executor/internal/taskscript/boot.sh +9 -9
- package/templates/_executor/internal/taskscript/cmd/main.go +2 -2
- package/templates/_executor/internal/taskscript/control.sh +15 -10
- package/templates/_executor/internal/taskscript/tasks.go +5 -3
- package/templates/_executor/lifecycle_test.go +11 -0
- package/templates/_executor/link_linux.go +28 -0
- package/templates/_executor/long_running_test.go +530 -0
- package/templates/_executor/main.go +72 -20
- package/templates/_executor/move_linux.go +490 -0
- package/templates/_executor/move_linux_test.go +460 -0
- package/templates/_executor/move_validation.go +46 -0
- package/templates/_executor/move_validation_test.go +187 -0
- package/templates/_executor/operations.go +926 -0
- package/templates/_executor/platform_linux.go +24 -173
- package/templates/_executor/platform_linux_test.go +9 -65
- package/templates/_executor/platform_other.go +30 -4
- package/templates/_executor/process.go +3 -0
- package/templates/_executor/records.go +242 -0
- package/templates/_executor/scope_cancel_contract_test.go +91 -0
- package/templates/_executor/scope_client.go +37 -0
- package/templates/_executor/scope_contract.go +12 -0
- package/templates/_executor/selected_scope.go +146 -0
- package/templates/_executor/selected_scope_test.go +186 -0
- package/templates/_executor/status_disabled_test.go +33 -0
- package/templates/_executor/store.go +4 -3
- package/templates/_executor/supervise.go +109 -0
- package/templates/_executor/supervise_test.go +130 -0
- package/templates/_executor/task_race_review_test.go +1 -1
- package/templates/_executor/tasks.go +7 -8
- package/templates/_executor/tasks_review_test.go +1 -1
- package/templates/_executor/tasks_test.go +4 -4
- package/templates/_executor/watch.go +8 -0
- package/templates/_executor/watch_test.go +23 -2
- package/templates/_executor/write_review_linux_test.go +4 -12
- package/templates/_shared/AGENTS.md +129 -0
- package/templates/binary/dsm-package.yaml +24 -0
- package/templates/binary/i18n/messages/de.json +4 -0
- package/templates/binary/i18n/messages/en.json +4 -0
- package/templates/binary/i18n/messages/es.json +4 -0
- package/templates/binary/i18n/messages/fr.json +4 -0
- package/templates/binary/i18n/messages/it.json +4 -0
- package/templates/binary/i18n/messages/ja.json +4 -0
- package/templates/binary/i18n/messages/ko.json +4 -0
- package/templates/binary/i18n/messages/zh-CN.json +4 -0
- package/templates/binary/i18n/messages/zh-TW.json +4 -0
- package/templates/binary/ui/app.js +19 -4
- package/templates/native-authorization/AuthorizationGate.vue +5 -3
- package/templates/native-authorization/i18n/de.json +1 -0
- package/templates/native-authorization/i18n/en.json +1 -0
- package/templates/native-authorization/i18n/es.json +2 -1
- package/templates/native-authorization/i18n/fr.json +1 -0
- package/templates/native-authorization/i18n/it.json +11 -10
- package/templates/native-authorization/i18n/ja.json +1 -0
- package/templates/native-authorization/i18n/ko.json +10 -9
- package/templates/native-authorization/i18n/zh-CN.json +6 -5
- package/templates/native-authorization/i18n/zh-TW.json +6 -5
- package/templates/native-go/README.md +43 -10
- package/templates/native-go/backend/cmd/app/main.go +27 -7
- package/templates/native-go/backend/internal/api/entry_test.go +47 -0
- package/templates/native-go/backend/internal/api/permissions_test.go +3 -3
- package/templates/native-go/backend/internal/api/registry.go +11 -0
- package/templates/native-go/backend/internal/api/sdk-permissions.json +14 -1
- package/templates/native-go/backend/internal/api/server.go +18 -5
- package/templates/native-go/backend/internal/app/delegate_config.go +57 -0
- package/templates/native-go/backend/internal/app/delegate_config_test.go +28 -0
- package/templates/native-go/backend/internal/app/portal.go +30 -0
- package/templates/native-go/backend/internal/app/portal_test.go +55 -0
- package/templates/native-go/backend/internal/app/routes.go +8 -2
- package/templates/native-go/backend/internal/background/runner.go +1 -1
- package/templates/native-go/backend/internal/background/store.go +16 -14
- package/templates/native-go/backend/internal/dsm/admin.go +8 -2
- package/templates/native-go/backend/internal/dsm/admin_linux.go +43 -13
- package/templates/native-go/backend/internal/dsm/admin_linux_test.go +56 -0
- package/templates/native-go/backend/internal/dsm/admin_other.go +4 -1
- package/templates/native-go/backend/internal/dsm/apppriv.go +30 -0
- package/templates/native-go/backend/internal/dsm/bridge_file.go +75 -0
- package/templates/native-go/backend/internal/dsm/bridge_file_test.go +57 -0
- package/templates/native-go/backend/internal/dsm/entry.go +177 -0
- package/templates/native-go/backend/internal/dsm/entry_test.go +315 -0
- package/templates/native-go/backend/internal/dsm/native/admin-armv7.elf +0 -0
- package/templates/native-go/backend/internal/dsm/native/apppriv-aarch64.elf +0 -0
- package/templates/native-go/backend/internal/dsm/native/apppriv-armv7.elf +0 -0
- package/templates/native-go/backend/internal/dsm/native/apppriv-x86_64.elf +0 -0
- package/templates/native-go/backend/internal/dsm/native/apppriv_check.c +33 -0
- package/templates/native-go/backend/internal/dsm/native/build.sh +13 -4
- package/templates/native-go/backend/internal/dsm/native/provenance.json +9 -3
- package/templates/native-go/backend/internal/filestation/check_permission.go +71 -0
- package/templates/native-go/backend/internal/filestation/check_permission_test.go +77 -0
- package/templates/native-go/backend/internal/filestation/sdk_policy_test.go +39 -0
- package/templates/native-go/backend/internal/filestation/service.go +1 -1
- package/templates/native-go/backend/internal/portal/portal.go +138 -0
- package/templates/native-go/backend/internal/portal/portal_test.go +89 -0
- package/templates/native-go/dsm-package.yaml +20 -1
- package/templates/native-go/i18n/glossary.json +2 -1
- package/templates/native-go/i18n/messages/de.json +1 -0
- package/templates/native-go/i18n/messages/en.json +1 -0
- package/templates/native-go/i18n/messages/es.json +1 -0
- package/templates/native-go/i18n/messages/fr.json +1 -0
- package/templates/native-go/i18n/messages/it.json +1 -0
- package/templates/native-go/i18n/messages/ja.json +1 -0
- package/templates/native-go/i18n/messages/ko.json +1 -0
- package/templates/native-go/i18n/messages/zh-CN.json +1 -0
- package/templates/native-go/i18n/messages/zh-TW.json +1 -0
- package/templates/native-go/ui/app.css +10 -0
- package/templates/native-go/ui/app.js +11 -1
- package/templates/native-go-delegate/DELEGATE.md +126 -5
- package/templates/native-go-delegate/backend/cmd/background-worker/main.go +7 -0
- package/templates/native-go-delegate/backend/internal/app/delegate.go +11 -1
- package/templates/native-go-delegate/backend/internal/app/delegate_routes_test.go +14 -0
- package/templates/native-go-delegate/backend/internal/background/delegated_moves.go +270 -0
- package/templates/native-go-delegate/backend/internal/delegate/admission_recovery_test.go +410 -0
- package/templates/native-go-delegate/backend/internal/delegate/cancel_contract_test.go +95 -0
- package/templates/native-go-delegate/backend/internal/delegate/client.go +8 -0
- package/templates/native-go-delegate/backend/internal/delegate/destination.go +110 -0
- package/templates/native-go-delegate/backend/internal/delegate/destination_state.go +117 -0
- package/templates/native-go-delegate/backend/internal/delegate/destination_state_test.go +60 -0
- package/templates/native-go-delegate/backend/internal/delegate/destination_test.go +116 -0
- package/templates/native-go-delegate/backend/internal/delegate/example.go +81 -0
- package/templates/native-go-delegate/backend/internal/delegate/example_test.go +91 -0
- package/templates/native-go-delegate/backend/internal/delegate/executor_contract_test.go +511 -0
- package/templates/native-go-delegate/backend/internal/delegate/files.go +1 -1
- package/templates/native-go-delegate/backend/internal/delegate/long_running_routes.go +92 -0
- package/templates/native-go-delegate/backend/internal/delegate/long_running_routes_test.go +99 -0
- package/templates/native-go-delegate/backend/internal/delegate/move.go +348 -0
- package/templates/native-go-delegate/backend/internal/delegate/move_test.go +351 -0
- package/templates/native-go-delegate/backend/internal/delegate/operations_list.go +65 -0
- package/templates/native-go-delegate/backend/internal/delegate/operations_list_test.go +114 -0
- package/templates/native-go-delegate/backend/internal/delegate/retry_test.go +1 -1
- package/templates/native-go-delegate/backend/internal/delegate/service.go +58 -17
- package/templates/native-go-delegate/backend/internal/delegate/staging.go +121 -0
- package/templates/native-go-delegate/backend/internal/delegate/status_disabled_test.go +51 -0
- package/templates/native-go-delegate/backend/internal/delegate/testdata/executor_contract.txt +112 -0
- package/templates/native-go-delegate/backend/internal/delegate/transient_contract_test.go +137 -0
- package/templates/native-go-delegate/backend/internal/filestation/files.go +1 -31
- package/templates/native-go-delegate/frontend/src/CapabilityPanels.vue +30 -3
- package/templates/native-go-delegate/frontend/src/delegate/BackgroundSavePanel.vue +113 -0
- package/templates/native-go-delegate/frontend/src/delegate/copy.ts +5 -6
- package/templates/native-go-delegate/frontend/test/background-save.test.ts +121 -0
- package/templates/native-go-delegate/frontend/test/scope-settings.test.ts +49 -0
- package/templates/native-go-delegate/test.sh +1 -0
- package/templates/native-go-jobs/frontend/src/background/i18n/de.json +2 -2
- package/templates/native-go-jobs/frontend/src/background/i18n/en.json +2 -2
- package/templates/native-go-jobs/frontend/src/background/i18n/es.json +12 -12
- package/templates/native-go-jobs/frontend/src/background/i18n/fr.json +4 -4
- package/templates/native-go-jobs/frontend/src/background/i18n/it.json +17 -17
- package/templates/native-go-jobs/frontend/src/background/i18n/ja.json +7 -7
- package/templates/native-go-jobs/frontend/src/background/i18n/ko.json +15 -15
- package/templates/native-go-jobs/frontend/src/background/i18n/zh-CN.json +3 -3
- package/templates/native-go-jobs/frontend/src/background/i18n/zh-TW.json +2 -2
- package/templates/native-go-jobs/frontend/test/background.test.ts +25 -28
- package/templates/native-go-vue/README.md +4 -0
- package/templates/native-go-vue/frontend/package-lock.json +4 -4
- package/templates/native-go-vue/frontend/package.json +1 -1
- package/templates/native-go-vue/frontend/src/App.vue +18 -13
- package/templates/native-go-vue/frontend/src/context.ts +31 -0
- package/templates/native-go-vue/frontend/src/delegate/ExecutorSettings.vue +52 -8
- package/templates/native-go-vue/frontend/src/delegate/catalogs.ts +11 -0
- package/templates/native-go-vue/frontend/src/delegate/copy.test.ts +12 -0
- package/templates/native-go-vue/frontend/src/delegate/copy.ts +4 -4
- package/templates/native-go-vue/frontend/src/delegate/executor-setup.ts +4 -2
- package/templates/native-go-vue/frontend/src/delegate/facade.test.ts +100 -0
- package/templates/native-go-vue/frontend/src/delegate/facade.ts +85 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/de.json +46 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/en.json +12 -2
- package/templates/native-go-vue/frontend/src/delegate/i18n/es.json +46 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +46 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/it.json +46 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +46 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +46 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/save/de.json +42 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/save/en.json +42 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/save/es.json +42 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/save/fr.json +42 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/save/it.json +42 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/save/ja.json +42 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/save/ko.json +42 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-CN.json +42 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-TW.json +42 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/zh-CN.json +46 -0
- package/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +15 -5
- package/templates/native-go-vue/frontend/src/delegate/long-running.ts +57 -0
- package/templates/native-go-vue/frontend/src/delegate/save-copy.test.ts +12 -0
- package/templates/native-go-vue/frontend/src/delegate/save-copy.ts +24 -0
- package/templates/native-go-vue/frontend/src/delegate/scoped-picker.test.ts +12 -0
- package/templates/native-go-vue/frontend/src/delegate/scoped-picker.ts +21 -0
- package/templates/native-go-vue/frontend/src/files/files.test.ts +7 -0
- package/templates/native-go-vue/frontend/src/i18n/de.json +20 -0
- package/templates/native-go-vue/frontend/src/i18n/en.json +20 -0
- package/templates/native-go-vue/frontend/src/i18n/es.json +20 -0
- package/templates/native-go-vue/frontend/src/i18n/fr.json +20 -0
- package/templates/native-go-vue/frontend/src/i18n/it.json +20 -0
- package/templates/native-go-vue/frontend/src/i18n/ja.json +20 -0
- package/templates/native-go-vue/frontend/src/i18n/ko.json +20 -0
- package/templates/native-go-vue/frontend/src/i18n/zh-CN.json +20 -0
- package/templates/native-go-vue/frontend/src/i18n/zh-TW.json +20 -0
- package/templates/native-go-vue/frontend/src/i18n.ts +13 -0
- package/templates/native-go-vue/frontend/src/identity/LoginPage.vue +16 -14
- package/templates/native-go-vue/frontend/src/identity/i18n/de.json +27 -0
- package/templates/native-go-vue/frontend/src/identity/i18n/en.json +27 -0
- package/templates/native-go-vue/frontend/src/identity/i18n/es.json +27 -0
- package/templates/native-go-vue/frontend/src/identity/i18n/fr.json +27 -0
- package/templates/native-go-vue/frontend/src/identity/i18n/it.json +27 -0
- package/templates/native-go-vue/frontend/src/identity/i18n/ja.json +27 -0
- package/templates/native-go-vue/frontend/src/identity/i18n/ko.json +27 -0
- package/templates/native-go-vue/frontend/src/identity/i18n/zh-CN.json +27 -0
- package/templates/native-go-vue/frontend/src/identity/i18n/zh-TW.json +27 -0
- package/templates/native-go-vue/frontend/src/identity/i18n.ts +14 -0
- package/templates/native-go-vue/frontend/src/identity/session.test.ts +4 -0
- package/templates/native-go-vue/frontend/src/identity/session.ts +9 -8
- package/templates/native-go-vue/frontend/src/nas/NasPicker.vue +31 -12
- package/templates/native-go-vue/frontend/src/nas/destination.test.ts +48 -0
- package/templates/native-go-vue/frontend/src/nas/i18n/files/de.json +1 -1
- package/templates/native-go-vue/frontend/src/nas/i18n/files/en.json +1 -1
- package/templates/native-go-vue/frontend/src/nas/i18n/files/es.json +10 -10
- package/templates/native-go-vue/frontend/src/nas/i18n/files/fr.json +1 -1
- package/templates/native-go-vue/frontend/src/nas/i18n/files/it.json +12 -12
- package/templates/native-go-vue/frontend/src/nas/i18n/files/ja.json +15 -15
- package/templates/native-go-vue/frontend/src/nas/i18n/files/ko.json +12 -12
- package/templates/native-go-vue/frontend/src/nas/i18n/files/zh-CN.json +1 -1
- package/templates/native-go-vue/frontend/src/nas/i18n/files/zh-TW.json +1 -1
- package/templates/native-go-vue/frontend/src/portal/PortalSettings.vue +39 -0
- package/templates/native-go-vue/frontend/src/shell/i18n/fr.json +1 -1
- package/templates/native-go-vue/frontend/src/shell/i18n/ja.json +1 -1
- package/templates/native-go-vue/frontend/test/apps.test.ts +14 -0
- package/templates/native-go-vue/frontend/test/portal-settings.test.ts +71 -0
- package/templates/native-go-vue/frontend/test/shell-i18n.test.ts +50 -0
|
@@ -18,6 +18,10 @@ subscriptions; unmount disposes subscriptions and ignores late responses.
|
|
|
18
18
|
operations. `--with jobs` adds package-account jobs and schedules; it combines with root-ops and files. `--with files` enables current-session File Station access,
|
|
19
19
|
and the background picker uses the retained session provider.
|
|
20
20
|
|
|
21
|
+
With `portal:` declared (`synopkgland add portal`), the status page shows
|
|
22
|
+
`src/portal/PortalSettings.vue`: the DSM Application Portal link, the alias form
|
|
23
|
+
for administrators, and the Control Panel shortcut.
|
|
24
|
+
|
|
21
25
|
Use public library controls, the embedded theme, per-app modal scope and unique
|
|
22
26
|
Vue ID prefixes. Backend route permissions and DSM checks remain authoritative.
|
|
23
27
|
Generated build and unit tests do not establish NAS runtime acceptance.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"name": "native-dsm-vue-app",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@lucide/vue": "1.48.0",
|
|
10
|
-
"@synopackageland/ui": "0.8.
|
|
10
|
+
"@synopackageland/ui": "0.8.1",
|
|
11
11
|
"vue": "3.5.43"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
@@ -1147,9 +1147,9 @@
|
|
|
1147
1147
|
}
|
|
1148
1148
|
},
|
|
1149
1149
|
"node_modules/@synopackageland/ui": {
|
|
1150
|
-
"version": "0.8.
|
|
1151
|
-
"resolved": "https://registry.npmjs.org/@synopackageland/ui/-/ui-0.8.
|
|
1152
|
-
"integrity": "sha512-
|
|
1150
|
+
"version": "0.8.1",
|
|
1151
|
+
"resolved": "https://registry.npmjs.org/@synopackageland/ui/-/ui-0.8.1.tgz",
|
|
1152
|
+
"integrity": "sha512-3BykWpWrDGjIDVjdL0WeMwD0oO3z1/IIEishR9+2sYAL/spd3G9sEQL1J26jHl0EfvoPvwPZHNJdIL14JQJQYA==",
|
|
1153
1153
|
"license": "MIT",
|
|
1154
1154
|
"dependencies": {
|
|
1155
1155
|
"@internationalized/date": "3.12.4",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "native-dsm-vue-app", "private": true, "type": "module",
|
|
3
3
|
"scripts": {"build": "vue-tsc --noEmit && vite build", "test": "vitest run"},
|
|
4
|
-
"dependencies": {"@lucide/vue": "1.48.0", "@synopackageland/ui": "0.8.
|
|
4
|
+
"dependencies": {"@lucide/vue": "1.48.0", "@synopackageland/ui": "0.8.1", "vue": "3.5.43"},
|
|
5
5
|
"devDependencies": {"@vitejs/plugin-vue": "6.0.9", "vite": "7.3.6", "typescript": "5.9.3", "vue-tsc": "3.3.11", "vitest": "4.1.11", "jsdom": "27.4.0"}
|
|
6
6
|
}
|
|
@@ -2,15 +2,18 @@
|
|
|
2
2
|
import {computed, onMounted, onUnmounted, ref, watch} from 'vue'
|
|
3
3
|
import {AppRoot, ConfigProvider, Button, Callout, Form, FormActions, FormItem, SettingsSection, Textarea} from '@synopackageland/ui'
|
|
4
4
|
import {uiLocale} from './nas/locale'
|
|
5
|
+
import {appCopy} from './i18n'
|
|
5
6
|
import type {AppStatus, Context} from './context'
|
|
6
7
|
import AppShell from './shell/AppShell.vue'
|
|
7
8
|
import CapabilityPanels from './CapabilityPanels.vue'
|
|
8
9
|
import FilesPage from './files/FilesPage.vue'
|
|
10
|
+
import PortalSettings from './portal/PortalSettings.vue'
|
|
9
11
|
import {shellPages, type ShellPage} from './shell/pages'
|
|
10
12
|
import {createSession} from './identity/session'
|
|
11
13
|
import LoginPage from './identity/LoginPage.vue'
|
|
12
14
|
const props = defineProps<{ctx: Context}>()
|
|
13
15
|
const login = createSession(props.ctx)
|
|
16
|
+
const copy = computed(() => appCopy(props.ctx.locale))
|
|
14
17
|
const app = ref<AppStatus>()
|
|
15
18
|
const busy = ref(false)
|
|
16
19
|
const error = ref('')
|
|
@@ -25,7 +28,8 @@ const pages = computed(() => shellPages({background:backgroundEnabled}))
|
|
|
25
28
|
const subtitle = computed(() => app.value ? `${app.value.package} · ${app.value.user} · ${app.value.version}` : undefined)
|
|
26
29
|
function message(e: unknown) {
|
|
27
30
|
const code = (e as {code?: string})?.code || 'request_failed'
|
|
28
|
-
|
|
31
|
+
const errors = copy.value.errors as Record<string, string>
|
|
32
|
+
return (code !== 'request_failed' && errors[code]) || errors.request_failed!.replace('{code}', code)
|
|
29
33
|
}
|
|
30
34
|
async function refresh() {
|
|
31
35
|
if(disposed || busy.value || (login.enabled.value && !login.subject.value)) return
|
|
@@ -39,7 +43,7 @@ async function refresh() {
|
|
|
39
43
|
async function save() {
|
|
40
44
|
if(disposed || busy.value) return
|
|
41
45
|
busy.value = true; error.value='';notice.value=''
|
|
42
|
-
try {await login.call('greeting.set',{text:text.value});if(!disposed) notice.value=
|
|
46
|
+
try {await login.call('greeting.set',{text:text.value});if(!disposed) notice.value=copy.value.saved}
|
|
43
47
|
catch(e) {if(!disposed) error.value=message(e)} finally {if(!disposed) busy.value=false}
|
|
44
48
|
}
|
|
45
49
|
watch(login.subject, (subject, previous) => {
|
|
@@ -60,22 +64,23 @@ onUnmounted(()=>{disposed=true;unsubscribe?.();login.dispose();text.value=''})
|
|
|
60
64
|
<template>
|
|
61
65
|
<AppRoot modal-scope="app" style="height: 100%">
|
|
62
66
|
<ConfigProvider :locale="uiLocale(ctx.locale)">
|
|
63
|
-
<p v-if="!login.ready.value" role="status">
|
|
64
|
-
<LoginPage v-else-if="login.enabled.value && !login.subject.value" :session="login" />
|
|
67
|
+
<p v-if="!login.ready.value" role="status">{{ copy.checking }}</p>
|
|
68
|
+
<LoginPage v-else-if="login.enabled.value && !login.subject.value" :session="login" :locale="ctx.locale" />
|
|
65
69
|
<AppShell v-else v-model="page" :locale="ctx.locale" :pages="pages" :description="subtitle">
|
|
66
|
-
<template #actions><Button variant="secondary" :disabled="busy" @click="refresh">
|
|
67
|
-
<div class="integration-app" :aria-busy="busy" aria-label="
|
|
68
|
-
<p v-if="busy" role="status">
|
|
69
|
-
<Callout v-if="error" tone="danger" title="
|
|
70
|
+
<template #actions><Button variant="secondary" :disabled="busy" @click="refresh">{{ copy.refresh }}</Button><Button v-if="login.enabled.value" variant="secondary" :disabled="busy" @click="signOut">{{ copy.signOut }}</Button></template>
|
|
71
|
+
<div class="integration-app" :aria-busy="busy" :aria-label="copy.settings">
|
|
72
|
+
<p v-if="busy" role="status">{{ copy.loading }}</p>
|
|
73
|
+
<Callout v-if="error" tone="danger" :title="copy.actionRequired"><span role="alert">{{ error }}</span></Callout>
|
|
70
74
|
<p v-if="notice" role="status">{{ notice }}</p>
|
|
71
75
|
<div data-page="status" v-show="page==='status'">
|
|
72
|
-
<SettingsSection title="
|
|
76
|
+
<SettingsSection :title="copy.statusTitle" :collapsible="false">
|
|
73
77
|
<p v-if="app">{{ app.package }} · {{ app.user }} · {{ app.version }}</p>
|
|
74
|
-
<p v-else-if="error" role="status">
|
|
78
|
+
<p v-else-if="error" role="status">{{ copy.statusError }}</p>
|
|
75
79
|
</SettingsSection>
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
<PortalSettings v-if="ctx.portal && app" :key="app.user" :portal="ctx.portal" />
|
|
81
|
+
<SettingsSection v-if="app?.admin" :title="copy.greeting" :collapsible="false">
|
|
82
|
+
<Form @submit.prevent="save"><FormItem :label="copy.greeting"><Textarea v-model="text" :disabled="busy" /></FormItem>
|
|
83
|
+
<FormActions><Button variant="primary" :disabled="busy || !text.trim()" @click="save">{{ copy.save }}</Button></FormActions>
|
|
79
84
|
</Form>
|
|
80
85
|
</SettingsSection>
|
|
81
86
|
</div>
|
|
@@ -35,10 +35,41 @@ export interface HostAdapter {
|
|
|
35
35
|
subscribe(event: HostEvent, listener: () => void): HostSubscriptionResult
|
|
36
36
|
dispose(): void
|
|
37
37
|
}
|
|
38
|
+
export interface PortalStatus {
|
|
39
|
+
state: 'active' | 'inactive' | 'unavailable'
|
|
40
|
+
// Set when active: https://<the NAS address in use>/<alias>/
|
|
41
|
+
alias?: string
|
|
42
|
+
url?: string
|
|
43
|
+
// An administrator can set or clear the alias here; otherwise Control Panel or nothing.
|
|
44
|
+
canManage: boolean
|
|
45
|
+
controlPanel: boolean
|
|
46
|
+
defaultAlias: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Rejects {code}: portal_alias_invalid, portal_alias_reserved, portal_alias_in_use, admin_required, portal_unavailable.
|
|
50
|
+
export interface PortalAccess {
|
|
51
|
+
// SDK panel text in DSM's language, keyed like the error codes plus title, link, apply, …
|
|
52
|
+
messages: Record<string, string>
|
|
53
|
+
status(): Promise<PortalStatus>
|
|
54
|
+
apply(alias: string): Promise<PortalStatus>
|
|
55
|
+
disable(): Promise<PortalStatus>
|
|
56
|
+
openControlPanel(): void
|
|
57
|
+
// The SDK's plain panel; Vue apps use portal/PortalSettings.vue instead.
|
|
58
|
+
mountPanel(element: HTMLElement): {refresh(): Promise<void>; dispose(): void}
|
|
59
|
+
}
|
|
60
|
+
|
|
38
61
|
export interface Context {
|
|
62
|
+
// Explicit long-running opt-in; bind with createBackgroundSaveFacade and an
|
|
63
|
+
// owner-bound App job metadata provider. Legacy downloads use the other panel.
|
|
64
|
+
delegate?: import('./delegate/long-running').BackgroundSaveFacade
|
|
39
65
|
files?: {endpoint:string;token?:()=>string|undefined}
|
|
40
66
|
displayName?: string
|
|
41
67
|
executor?: import('./delegate/executor-setup').ExecutorSetup
|
|
68
|
+
// DSM's signed-in account. isAdmin only hides or disables administrator
|
|
69
|
+
// settings; the backend enforces access with permissions.json and RequireAdmin.
|
|
70
|
+
user?: {readonly name: string; readonly isAdmin: boolean}
|
|
71
|
+
// DSM Application Portal, present only when dsm-package.yaml declares portal.
|
|
72
|
+
portal?: PortalAccess
|
|
42
73
|
call<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T>
|
|
43
74
|
reopen(fn: () => void): () => void
|
|
44
75
|
locale: string
|
|
@@ -1,24 +1,68 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import {computed,onBeforeUnmount,onMounted,ref} from 'vue'
|
|
3
|
-
import {Button,Callout,Form,FormActions,FormItem,Input,SettingsSection} from '@synopackageland/ui'
|
|
3
|
+
import {Button,Callout,Form,FormActions,FormItem,Input,Select,SettingsSection} from '@synopackageland/ui'
|
|
4
|
+
import type {Context} from '../context'
|
|
5
|
+
import type {ScopeSelection} from './long-running'
|
|
4
6
|
import {useExecutorSetup,type CapabilityStatus} from './executor-setup'
|
|
5
7
|
import {delegateCopy} from './copy'
|
|
6
|
-
const props=defineProps<{admin:boolean;locale?:string}>()
|
|
8
|
+
const props=defineProps<{admin:boolean;locale?:string;ctx?:Pick<Context,'call'>}>()
|
|
9
|
+
const emit=defineEmits<{confirmed:[]}>()
|
|
7
10
|
const setup=useExecutorSetup(),copy=computed(()=>delegateCopy(props.locale))
|
|
8
11
|
const status=ref<CapabilityStatus>(),password=ref(''),busy=ref(false),error=ref(false)
|
|
12
|
+
const scope=ref<{scope_mode?:string;preselection?:string;selection?:ScopeSelection|null;mismatch?:boolean}>(),chosen=ref(''),shares=ref<Array<{label:string;value:string}>>([])
|
|
13
|
+
const dynamic=computed(()=>scope.value?.scope_mode==='install-selected')
|
|
14
|
+
const options=computed(()=>[{label:copy.value.homeScope,value:'home'},...shares.value])
|
|
15
|
+
const canConfirm=computed(()=>!dynamic.value||!!chosen.value&&options.value.some(o=>o.value===chosen.value))
|
|
16
|
+
const needsScope=computed(()=>dynamic.value&&!scope.value?.selection)
|
|
17
|
+
const changed=computed(()=>chosen.value!==scope.value?.selection?.scope)
|
|
9
18
|
let disposed=false
|
|
10
|
-
async function
|
|
11
|
-
|
|
19
|
+
async function load(approved?:CapabilityStatus){
|
|
20
|
+
const next=approved??await setup.status()
|
|
21
|
+
if(disposed)return
|
|
22
|
+
status.value=next
|
|
23
|
+
if(!props.ctx)return
|
|
24
|
+
const detail=await props.ctx.call<NonNullable<typeof scope.value>>('delegate.status')
|
|
25
|
+
if(disposed)return
|
|
26
|
+
scope.value=detail
|
|
27
|
+
if(detail.scope_mode!=='install-selected')return
|
|
28
|
+
const all:Array<{label:string;value:string}>=[]
|
|
29
|
+
let offset=0
|
|
30
|
+
for(let count=0;count<128;count++){
|
|
31
|
+
const page=await props.ctx.call<{shares:Array<{name:string;path:string;isdir:boolean}>;total:number;offset:number}>('bg.browse',{offset})
|
|
32
|
+
if(disposed)return
|
|
33
|
+
if(!Array.isArray(page.shares)||!Number.isSafeInteger(page.total)||page.total<0||!page.shares.length&&offset<page.total)throw {code:'EXECUTOR_PROTOCOL'}
|
|
34
|
+
for(const entry of page.shares){if(entry.isdir&&entry.path==='/'+entry.name&&!['home','homes'].includes(entry.name.toLowerCase()))all.push({label:entry.name,value:'shared:'+entry.name})}
|
|
35
|
+
offset+=page.shares.length
|
|
36
|
+
if(offset>=page.total)break
|
|
37
|
+
if(count===127)throw {code:'EXECUTOR_PROTOCOL'}
|
|
38
|
+
}
|
|
39
|
+
shares.value=all
|
|
40
|
+
chosen.value=detail.selection?.scope??detail.preselection??''
|
|
41
|
+
}
|
|
42
|
+
async function refresh(){if(!props.admin||busy.value)return;busy.value=true;error.value=false;try{await load()}catch{if(!disposed)error.value=true}finally{if(!disposed)busy.value=false}}
|
|
43
|
+
async function authorize(){
|
|
44
|
+
if(!props.admin||busy.value||!password.value||!canConfirm.value||error.value)return
|
|
45
|
+
busy.value=true;error.value=false;const entered=password.value;password.value=''
|
|
46
|
+
try{const next=dynamic.value?await setup.authorize(entered,chosen.value):await setup.authorize(entered);await load(next);if(!disposed)emit('confirmed')}
|
|
47
|
+
catch{if(!disposed)error.value=true}
|
|
48
|
+
finally{if(!disposed)busy.value=false}
|
|
49
|
+
}
|
|
12
50
|
onMounted(()=>void refresh());onBeforeUnmount(()=>{disposed=true;password.value=''})
|
|
13
51
|
</script>
|
|
14
52
|
<template>
|
|
15
53
|
<SettingsSection v-if="admin" :title="copy.setupTitle" :collapsible="false" :aria-busy="busy">
|
|
16
54
|
<p>{{ copy.setupHelp }}</p>
|
|
17
55
|
<Callout v-if="error" tone="danger" role="alert">{{ copy.setupError }}</Callout>
|
|
18
|
-
<p role="status">{{ status?.state==='ready'?copy.ready:status?.state==='needs-admin-approval'?copy.needs:copy.blocked }}</p>
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
56
|
+
<p role="status">{{ status?.state==='ready'?(needsScope?copy.scopeNeeds:copy.ready):status?.state==='needs-admin-approval'?copy.needs:copy.blocked }}</p>
|
|
57
|
+
<p v-if="scope?.selection">{{ copy.protectedScope }}: {{ scope.selection.root }}</p>
|
|
58
|
+
<Callout v-if="scope?.mismatch" tone="warning">{{ copy.scopeMismatch }} {{ copy.preselection }}: {{ scope.preselection }} · {{ copy.protectedScope }}: {{ scope.selection?.root }}</Callout>
|
|
59
|
+
<Form v-if="status?.state==='needs-admin-approval'||dynamic&&status?.state==='ready'" @submit.prevent="authorize">
|
|
60
|
+
<template v-if="dynamic">
|
|
61
|
+
<p>{{ scope?.selection?copy.scopeChangeHelp:copy.preselectionHelp }}</p>
|
|
62
|
+
<FormItem :label="copy.scopeLabel" label-for="delegate-scope"><Select id="delegate-scope" v-model="chosen" :options="options" :disabled="busy||error" /></FormItem>
|
|
63
|
+
</template>
|
|
64
|
+
<FormItem :label="copy.password" label-for="delegate-password"><Input id="delegate-password" v-model="password" type="password" autocomplete="current-password" :disabled="busy||error" /></FormItem>
|
|
65
|
+
<FormActions><Button type="submit" variant="primary" :disabled="busy||error||!password||!canConfirm||dynamic&&!!scope?.selection&&!changed">{{ dynamic?(scope?.selection?copy.confirmScope:copy.confirmShare):copy.authorize }}</Button></FormActions>
|
|
22
66
|
</Form>
|
|
23
67
|
<Button variant="secondary" :disabled="busy" @click="refresh">{{ copy.refresh }}</Button>
|
|
24
68
|
</SettingsSection>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import en from './i18n/en.json'
|
|
2
|
+
import zhTW from './i18n/zh-TW.json'
|
|
3
|
+
import zhCN from './i18n/zh-CN.json'
|
|
4
|
+
import ja from './i18n/ja.json'
|
|
5
|
+
import ko from './i18n/ko.json'
|
|
6
|
+
import de from './i18n/de.json'
|
|
7
|
+
import fr from './i18n/fr.json'
|
|
8
|
+
import es from './i18n/es.json'
|
|
9
|
+
import it from './i18n/it.json'
|
|
10
|
+
export type DelegateCopy=typeof en
|
|
11
|
+
export const delegateCatalogs:Record<string,DelegateCopy>={en,'zh-TW':zhTW,'zh-CN':zhCN,ja,ko,de,fr,es,it}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {expect,test} from 'vitest'
|
|
2
|
+
import {delegateCopy} from './copy'
|
|
3
|
+
import {saveCopy} from './save-copy'
|
|
4
|
+
import zhCN from './i18n/zh-CN.json'
|
|
5
|
+
import saveJa from './i18n/save/ja.json'
|
|
6
|
+
test('delegate and save copy follow the DSM language across all nine catalogs',()=>{
|
|
7
|
+
expect(delegateCopy('chs')).toMatchObject(zhCN)
|
|
8
|
+
expect(delegateCopy('zh_CN')).toMatchObject(zhCN)
|
|
9
|
+
expect(saveCopy('jpn')).toEqual(saveJa)
|
|
10
|
+
expect(delegateCopy('unknown').refresh).toBe('Refresh')
|
|
11
|
+
expect(saveCopy().refresh).toBe('Refresh')
|
|
12
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
export function delegateCopy(locale='en'){return locale
|
|
4
|
-
export function errorCopy(error:unknown,copy:
|
|
1
|
+
import {uiLocale} from '../nas/locale'
|
|
2
|
+
import {delegateCatalogs,type DelegateCopy} from './catalogs'
|
|
3
|
+
export function delegateCopy(locale='en'){return delegateCatalogs[uiLocale(locale)]!}
|
|
4
|
+
export function errorCopy(error:unknown,copy:DelegateCopy){const code=(error as {code?:string})?.code;return ['session_required','AUTH_DENIED'].includes(code??'')?copy.session:['DENIED','SCOPE_DENIED','PERMISSION_DENIED','APP_DENIED','INELIGIBLE','denied'].includes(code??'')?copy.denied:copy.error}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {inject,type InjectionKey} from 'vue'
|
|
2
2
|
export type CapabilityState='unsupported'|'needs-admin-approval'|'ready'|'blocked'|'incompatible'
|
|
3
|
-
|
|
3
|
+
// requested: a scope change whose root task may still land; it applied once
|
|
4
|
+
// status() reports that scope with a generation above this one.
|
|
5
|
+
export interface CapabilityStatus {state:CapabilityState;reason?:string;requested?:{scope:string;generation:number}}
|
|
4
6
|
// p2-setup supplies the implementation through app.provide(ExecutorSetupKey, setup).
|
|
5
7
|
// Passwords belong only to this call and must never be persisted or logged.
|
|
6
8
|
export interface ExecutorSetup {
|
|
7
9
|
status():Promise<CapabilityStatus>
|
|
8
|
-
authorize(password:string):Promise<CapabilityStatus>
|
|
10
|
+
authorize(password:string,selectedScope?:string):Promise<CapabilityStatus>
|
|
9
11
|
control(action:'start'|'stop'|'purge'):Promise<CapabilityStatus>
|
|
10
12
|
}
|
|
11
13
|
export const ExecutorSetupKey:InjectionKey<ExecutorSetup>=Symbol('executor-setup')
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {expect,test,vi} from 'vitest'
|
|
2
|
+
import {createBackgroundSaveFacade} from './facade'
|
|
3
|
+
test('destination lookup returns protected selection and rejects a grant outside its root',async()=>{
|
|
4
|
+
const selection={scope:'shared:reports',root:'/reports',generation:2}
|
|
5
|
+
const destination={id:'mine',key:'save',path:'/reports/exports',username:'alice',lifetime:'until-revoked'}
|
|
6
|
+
const call=vi.fn(async()=>({key:'save',selection,destination,status:'active'}))
|
|
7
|
+
const sdk=createBackgroundSaveFacade({call:call as any})
|
|
8
|
+
expect(await sdk.destination('save')).toEqual({key:'save',selection,destination,status:'active'})
|
|
9
|
+
expect(call).toHaveBeenCalledExactlyOnceWith('delegate.destination',{key:'save'})
|
|
10
|
+
call.mockResolvedValue({key:'save',selection,destination:{...destination,path:'/other'},status:'active'})
|
|
11
|
+
await expect(sdk.destination('save')).rejects.toMatchObject({code:'EXECUTOR_PROTOCOL'})
|
|
12
|
+
})
|
|
13
|
+
const ref={delegation_id:'mine',job_id:'original'}
|
|
14
|
+
test('operation cancellation keeps its bounded readable cause through the facade',async()=>{
|
|
15
|
+
const call=vi.fn(async()=>({job_id:'original',state:'canceled',code:'OPERATION_CANCELED',reason:'grant-revoked'}))
|
|
16
|
+
const sdk=createBackgroundSaveFacade({call:call as any})
|
|
17
|
+
expect(await sdk.cancel(ref)).toMatchObject({state:'canceled',reason:'grant-revoked'})
|
|
18
|
+
for(const reason of [123, 'x'.repeat(129), 'raw DSM response /path']){
|
|
19
|
+
call.mockResolvedValue({...await call(),reason} as any)
|
|
20
|
+
await expect(sdk.cancel(ref)).rejects.toMatchObject({code:'EXECUTOR_PROTOCOL'})
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
test('a copy cancel acknowledgement passes through unchanged for same-ref polling',async()=>{
|
|
24
|
+
const ack={job_id:'original',state:'copying',code:'CANCEL_REQUESTED',reason:'operation-canceled-by-user'}
|
|
25
|
+
const call=vi.fn(async()=>ack)
|
|
26
|
+
expect(await createBackgroundSaveFacade({call:call as any}).cancel(ref)).toEqual(ack)
|
|
27
|
+
expect(call).toHaveBeenCalledWith('delegate.operations.cancel',ref)
|
|
28
|
+
})
|
|
29
|
+
const grant={delegation_id:'mine',destination:'save',path:'/home/Exports',subject:{username:'normal1',uid:1205},lifetime:'until-revoked',revoked:false}
|
|
30
|
+
function fixture(){
|
|
31
|
+
const call=vi.fn(async(method:string)=>{
|
|
32
|
+
if(method==='delegate.status')return {state:'ready',disabled:false}
|
|
33
|
+
if(method==='delegate.list')return {delegations:[grant,{...grant,delegation_id:'revoked',revoked:true},{...grant,delegation_id:'legacy',lifetime:''}]}
|
|
34
|
+
if(method==='delegate.operations.get')return {job_id:'original',state:'committed',receipt:{path:'/home/Exports/report.csv',size:4}}
|
|
35
|
+
if(method==='delegate.operations.cancel')return {job_id:'original',state:'canceled'}
|
|
36
|
+
if(method==='delegate.revoke')return {revoked:true}
|
|
37
|
+
if(method==='delegate.disable')return {disabled:true}
|
|
38
|
+
if(method==='delegate.enable')return {disabled:false}
|
|
39
|
+
if(method==='delegate.select_destination')return {id:'mine',key:'save',path:'/home/Exports',username:'normal1',lifetime:'until-revoked'}
|
|
40
|
+
throw {code:'unknown_method'}
|
|
41
|
+
})
|
|
42
|
+
const jobs=vi.fn(async()=>[{...ref,name:'report.csv'}])
|
|
43
|
+
return {call,jobs,sdk:createBackgroundSaveFacade({call:call as any},jobs)}
|
|
44
|
+
}
|
|
45
|
+
test('W2 list is mapped to active permanent destinations without changing owner or legacy semantics',async()=>{
|
|
46
|
+
const {sdk,call}=fixture()
|
|
47
|
+
expect(await sdk.list()).toEqual([{id:'mine',key:'save',path:'/home/Exports',username:'normal1',lifetime:'until-revoked'}])
|
|
48
|
+
expect(call).toHaveBeenCalledExactlyOnceWith('delegate.list')
|
|
49
|
+
await sdk.selectDestination({key:'save',path:'/home/Exports'})
|
|
50
|
+
expect(call).toHaveBeenLastCalledWith('delegate.select_destination',{key:'save',path:'/home/Exports'})
|
|
51
|
+
})
|
|
52
|
+
test('App metadata is joined with protected status by the original two IDs, never a synthetic list route',async()=>{
|
|
53
|
+
const {sdk,call,jobs}=fixture()
|
|
54
|
+
expect(await sdk.operations()).toEqual([{...ref,name:'report.csv',state:'committed',receipt:{path:'/home/Exports/report.csv',size:4}}])
|
|
55
|
+
expect(jobs).toHaveBeenCalledOnce()
|
|
56
|
+
expect(call).toHaveBeenCalledExactlyOnceWith('delegate.operations.get',ref)
|
|
57
|
+
await sdk.cancel(ref);expect(call).toHaveBeenLastCalledWith('delegate.operations.cancel',ref)
|
|
58
|
+
await sdk.revoke({delegation_id:'mine'});expect(call).toHaveBeenLastCalledWith('delegate.revoke',{delegation_id:'mine'})
|
|
59
|
+
})
|
|
60
|
+
test('missing protected disabled status cannot be reported as enabled',async()=>{
|
|
61
|
+
const {sdk,call}=fixture();call.mockResolvedValue({state:'ready'} as any)
|
|
62
|
+
await expect(sdk.status()).rejects.toMatchObject({code:'EXECUTOR_PROTOCOL'})
|
|
63
|
+
})
|
|
64
|
+
test('malformed grant and App metadata references cannot be coerced into valid IDs',async()=>{
|
|
65
|
+
const {sdk,call}=fixture()
|
|
66
|
+
call.mockResolvedValue({delegations:[{...grant,delegation_id:undefined}]} as any)
|
|
67
|
+
await expect(sdk.list()).rejects.toMatchObject({code:'EXECUTOR_PROTOCOL'})
|
|
68
|
+
const invalid=createBackgroundSaveFacade({call:call as any},async()=>[{job_id:undefined,delegation_id:'mine',name:'report.csv'}] as any)
|
|
69
|
+
await expect(invalid.operations()).rejects.toMatchObject({code:'EXECUTOR_PROTOCOL'})
|
|
70
|
+
expect(call).toHaveBeenCalledTimes(1)
|
|
71
|
+
})
|
|
72
|
+
test('malformed or mismatched operation evidence never becomes Saved',async()=>{
|
|
73
|
+
for(const value of [{job_id:'other',state:'committed',receipt:{path:'/home/a',size:1}},{job_id:'original',state:'committed'},{job_id:'original',state:'copying',receipt:{path:'/home/a',size:1}},{job_id:'original',state:'committed',receipt:{path:'/home/a',size:-1}}]){
|
|
74
|
+
const {sdk,call}=fixture();call.mockResolvedValue(value as any)
|
|
75
|
+
await expect(sdk.operations()).rejects.toMatchObject({code:'EXECUTOR_PROTOCOL'})
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
test('read timeout never admits or resubmits an operation',async()=>{
|
|
79
|
+
const {sdk,call}=fixture();call.mockRejectedValue({code:'EXECUTOR_TIMEOUT'})
|
|
80
|
+
await expect(sdk.operations()).rejects.toMatchObject({code:'EXECUTOR_TIMEOUT'})
|
|
81
|
+
expect(call).toHaveBeenCalledExactlyOnceWith('delegate.operations.get',ref)
|
|
82
|
+
})
|
|
83
|
+
test('disable and enable use authenticated protected routes, not mailbox controls',async()=>{
|
|
84
|
+
const {sdk,call}=fixture()
|
|
85
|
+
expect(await sdk.status()).toEqual({disabled:false})
|
|
86
|
+
expect(await sdk.disable()).toEqual({disabled:true});expect(call).toHaveBeenLastCalledWith('delegate.disable')
|
|
87
|
+
expect(await sdk.enable()).toEqual({disabled:false});expect(call).toHaveBeenLastCalledWith('delegate.enable')
|
|
88
|
+
call.mockResolvedValue({} as any);await expect(sdk.disable()).rejects.toMatchObject({code:'EXECUTOR_PROTOCOL'})
|
|
89
|
+
})
|
|
90
|
+
test('the default facade lists backend mirrors but ignores their committed claim until live receipt',async()=>{
|
|
91
|
+
const {call}=fixture()
|
|
92
|
+
call.mockImplementation(async(method:string)=>{
|
|
93
|
+
if(method==='delegate.operations.list')return {operations:[{...ref,name:'report.csv',state:'committed'}]} as any
|
|
94
|
+
if(method==='delegate.operations.get')return {job_id:'original',state:'uncertain'} as any
|
|
95
|
+
throw {code:'unexpected'}
|
|
96
|
+
})
|
|
97
|
+
const sdk=createBackgroundSaveFacade({call:call as any})
|
|
98
|
+
expect(await sdk.operations()).toEqual([{...ref,name:'report.csv',state:'uncertain'}])
|
|
99
|
+
expect(call.mock.calls).toEqual([['delegate.operations.list'],['delegate.operations.get',ref]])
|
|
100
|
+
})
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type {Context} from '../context'
|
|
2
|
+
import type {BackgroundSaveFacade,DestinationGrant,DestinationState,ScopeSelection,OperationRef,OperationResult,SaveOperation} from './long-running'
|
|
3
|
+
// Supply an owner-bound App backend view, not browser storage or an executor list.
|
|
4
|
+
// Producers persist these references alongside their own business job metadata.
|
|
5
|
+
export interface AppSaveJob extends OperationRef {name:string;refusal?:string}
|
|
6
|
+
const token=/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/
|
|
7
|
+
const id=(value:unknown):value is string=>typeof value==='string'&&token.test(value)
|
|
8
|
+
const validRef=(ref:OperationRef)=>ref&&id(ref.delegation_id)&&id(ref.job_id)
|
|
9
|
+
const path=(value:unknown):value is string=>typeof value==='string'&&value.startsWith('/')&&value!=='/'&&!value.includes('\0')&&!value.slice(1).split('/').some(part=>!part||part==='.'||part==='..')
|
|
10
|
+
function protocol():never{throw {code:'EXECUTOR_PROTOCOL'}}
|
|
11
|
+
function validSelection(value:ScopeSelection){return value&&Number.isSafeInteger(value.generation)&&value.generation>=0&&path(value.root)&&(value.scope==='home'?value.root==='/home':value.scope.startsWith('shared:')&&value.root==='/'+value.scope.slice(7))}
|
|
12
|
+
function result(value:OperationResult,ref:OperationRef):OperationResult{
|
|
13
|
+
if(!value||value.job_id!==ref.job_id||!['queued','copying','committed','canceled','interrupted','uncertain'].includes(value.state))protocol()
|
|
14
|
+
if(value.state==='committed'){
|
|
15
|
+
if(!value.receipt||!path(value.receipt.path)||!Number.isSafeInteger(value.receipt.size)||value.receipt.size<0||value.receipt.size>16*1024**3)protocol()
|
|
16
|
+
}else if(value.receipt!==undefined)protocol()
|
|
17
|
+
if(value.reason!==undefined&&(typeof value.reason!=='string'||!/^[A-Za-z][A-Za-z0-9_-]{0,127}$/.test(value.reason)))protocol()
|
|
18
|
+
return {job_id:value.job_id,state:value.state,...(value.receipt?{receipt:{...value.receipt}}:{}),...(value.code?{code:value.code}:{}),...(value.reason?{reason:value.reason}:{})}
|
|
19
|
+
}
|
|
20
|
+
/** Bind the portable UI facade to W2's existing authenticated routes. */
|
|
21
|
+
export function createBackgroundSaveFacade(ctx:Pick<Context,'call'>,jobs:()=>Promise<readonly AppSaveJob[]>=async()=>{
|
|
22
|
+
const out=await ctx.call<{operations:AppSaveJob[]}>('delegate.operations.list')
|
|
23
|
+
if(!Array.isArray(out?.operations))protocol()
|
|
24
|
+
return out.operations
|
|
25
|
+
}):BackgroundSaveFacade{
|
|
26
|
+
const operation=async(method:string,ref:OperationRef)=>{
|
|
27
|
+
if(!validRef(ref))protocol()
|
|
28
|
+
return result(await ctx.call<OperationResult>(method,{delegation_id:ref.delegation_id,job_id:ref.job_id}),ref)
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
async destination(key){
|
|
32
|
+
if(!id(key))protocol()
|
|
33
|
+
const out=await ctx.call<DestinationState>('delegate.destination',{key})
|
|
34
|
+
if(!out||out.key!==key||!['active','missing','reselection-required','needs-scope-confirmation','disabled'].includes(out.status)||out.selection!==null&&!validSelection(out.selection))protocol()
|
|
35
|
+
if(out.status==='active'){
|
|
36
|
+
const g=out.destination,s=out.selection
|
|
37
|
+
if(!g||!s||!id(g.id)||g.key!==key||g.lifetime!=='until-revoked'||!g.username||!path(g.path)||!(g.path===s.root||g.path.startsWith(s.root+'/')))protocol()
|
|
38
|
+
}else if(out.destination!==null)protocol()
|
|
39
|
+
return out
|
|
40
|
+
},
|
|
41
|
+
async exportExample(input){
|
|
42
|
+
if(!id(input.key)||!id(input.job_id))protocol()
|
|
43
|
+
const out=await ctx.call<OperationRef>('delegate.example.export',{key:input.key,job_id:input.job_id})
|
|
44
|
+
if(!validRef(out)||out.job_id!==input.job_id)protocol()
|
|
45
|
+
return out
|
|
46
|
+
},
|
|
47
|
+
selectDestination:input=>ctx.call<DestinationGrant>('delegate.select_destination',{key:input.key,path:input.path}),
|
|
48
|
+
async status(){
|
|
49
|
+
const value=await ctx.call<{state:string;disabled?:boolean;scope_mode?:string;preselection?:string;selection?:ScopeSelection|null;mismatch?:boolean}>('delegate.status')
|
|
50
|
+
// Capability readiness and the protected admission switch are independent.
|
|
51
|
+
if(!['ready','disabled','needs-scope-confirmation'].includes(value?.state))throw {code:'EXECUTOR_UNAVAILABLE'}
|
|
52
|
+
if(typeof value?.disabled!=='boolean')protocol()
|
|
53
|
+
if(value.selection&&!validSelection(value.selection))protocol()
|
|
54
|
+
return {disabled:value.disabled,...(value.scope_mode?{scopeMode:value.scope_mode,selection:value.selection??null,preselection:value.preselection,mismatch:value.mismatch}:{} )}
|
|
55
|
+
},
|
|
56
|
+
async list(){
|
|
57
|
+
const value=await ctx.call<{delegations:Array<{delegation_id:string;destination:string;path:string;subject:{username:string};lifetime:string;revoked:boolean}>}>('delegate.list')
|
|
58
|
+
if(!Array.isArray(value?.delegations))protocol()
|
|
59
|
+
return value.delegations.filter(g=>g.lifetime==='until-revoked'&&!g.revoked).map(g=>{
|
|
60
|
+
if(!id(g.delegation_id)||typeof g.destination!=='string'||!g.destination||!path(g.path)||typeof g.subject?.username!=='string'||!g.subject.username)protocol()
|
|
61
|
+
return {id:g.delegation_id,key:g.destination,path:g.path,username:g.subject.username,lifetime:'until-revoked' as const}
|
|
62
|
+
})
|
|
63
|
+
},
|
|
64
|
+
async operations(){
|
|
65
|
+
const metadata=await jobs()
|
|
66
|
+
return Promise.all(metadata.map(async job=>{
|
|
67
|
+
if(typeof job?.name!=='string'||!job.name||!validRef(job))protocol()
|
|
68
|
+
const ref={delegation_id:job.delegation_id,job_id:job.job_id}
|
|
69
|
+
try{return {...ref,name:job.name,...await operation('delegate.operations.get',ref)} satisfies SaveOperation}
|
|
70
|
+
catch(e){
|
|
71
|
+
if(job.refusal&&(e as {code?:string})?.code==='operation-not-found')return {...ref,name:job.name,state:'interrupted',code:job.refusal} satisfies SaveOperation
|
|
72
|
+
throw e
|
|
73
|
+
}
|
|
74
|
+
}))
|
|
75
|
+
},
|
|
76
|
+
async revoke(input){
|
|
77
|
+
const out=await ctx.call<{revoked:boolean}>('delegate.revoke',{delegation_id:input.delegation_id})
|
|
78
|
+
if(out?.revoked!==true)protocol()
|
|
79
|
+
return {revoked:true}
|
|
80
|
+
},
|
|
81
|
+
cancel:ref=>operation('delegate.operations.cancel',ref),
|
|
82
|
+
async disable(){const out=await ctx.call<{disabled:boolean}>('delegate.disable');if(out?.disabled!==true)protocol();return {disabled:true}},
|
|
83
|
+
async enable(){const out=await ctx.call<{disabled:boolean}>('delegate.enable');if(out?.disabled!==false)protocol();return {disabled:false}},
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Dateizugriff im Hintergrund",
|
|
3
|
+
"help": "Downloads laufen weiter, nachdem Sie diese App schließen. Jeder Auftrag darf bis zu einer Stunde lang nur in den ausgewählten Ordner schreiben. Vorhandene Dateien werden nie ersetzt.",
|
|
4
|
+
"setupTitle": "Administratoreinrichtung",
|
|
5
|
+
"setupHelp": "Genehmigen Sie diese App einmal, damit sie Dateiaufträge abschließen kann, während Benutzer offline sind.",
|
|
6
|
+
"password": "DSM-Passwort",
|
|
7
|
+
"authorize": "Dateizugriff im Hintergrund genehmigen",
|
|
8
|
+
"confirmShare": "Freigegebenen Ordner bestätigen",
|
|
9
|
+
"scopeNeeds": "Der freigegebene Ordner muss bestätigt werden. Wählen Sie unten einen freigegebenen Ordner und bestätigen Sie ihn mit Ihrem DSM-Passwort.",
|
|
10
|
+
"ready": "Der Dateizugriff im Hintergrund ist bereit.",
|
|
11
|
+
"needs": "Ein Administrator muss den Dateizugriff im Hintergrund genehmigen.",
|
|
12
|
+
"blocked": "Der Dateizugriff im Hintergrund ist nicht verfügbar. Bitten Sie einen Administrator, die App-Einrichtung zu prüfen.",
|
|
13
|
+
"refresh": "Aktualisieren",
|
|
14
|
+
"revoke": "Widerrufen",
|
|
15
|
+
"cancel": "Auftrag abbrechen",
|
|
16
|
+
"noDelegations": "Sie haben keine aktiven Delegierungen.",
|
|
17
|
+
"delegations": "Meine Delegierungen",
|
|
18
|
+
"expires": "Ablauf",
|
|
19
|
+
"downloads": "Meine Downloads",
|
|
20
|
+
"noJobs": "Sie haben keine Download-Aufträge.",
|
|
21
|
+
"url": "Download-URL",
|
|
22
|
+
"name": "Dateiname",
|
|
23
|
+
"choose": "Ordner auswählen",
|
|
24
|
+
"submit": "In meinen Ordner herunterladen",
|
|
25
|
+
"destination": "Ziel",
|
|
26
|
+
"pending": "Auftrag abgebrochen. Der Widerruf steht noch aus; die App versucht es erneut.",
|
|
27
|
+
"error": "Die Aktion konnte nicht abgeschlossen werden. Aktualisieren Sie die Ansicht und versuchen Sie es erneut. Wenn der Fehler bestehen bleibt, wenden Sie sich an Ihren Administrator.",
|
|
28
|
+
"setupError": "Die Genehmigung konnte nicht abgeschlossen werden. Geben Sie Ihr DSM-Passwort erneut ein und versuchen Sie es noch einmal, oder prüfen Sie die App-Einrichtung.",
|
|
29
|
+
"denied": "Der Zugriff wurde verweigert. Prüfen Sie Ihre Ordnerberechtigungen und versuchen Sie es erneut.",
|
|
30
|
+
"session": "Melden Sie sich erneut bei DSM an und aktualisieren Sie dann die Ansicht.",
|
|
31
|
+
"size": "Downloads sind auf 64 MB und 60 Sekunden begrenzt.",
|
|
32
|
+
"queued": "In Warteschlange",
|
|
33
|
+
"running": "Wird heruntergeladen",
|
|
34
|
+
"succeeded": "Gespeichert",
|
|
35
|
+
"failed": "Fehlgeschlagen",
|
|
36
|
+
"canceled": "Abgebrochen",
|
|
37
|
+
"interrupted": "Unterbrochen",
|
|
38
|
+
"homeScope": "Home-Ordner jedes Benutzers",
|
|
39
|
+
"scopeLabel": "Bereich für Speichern im Hintergrund",
|
|
40
|
+
"protectedScope": "Bestätigter Bereich",
|
|
41
|
+
"preselection": "Vorauswahl bei der Installation",
|
|
42
|
+
"preselectionHelp": "Die Vorauswahl bei der Installation ist nur ein Vorschlag. Bestätigen Sie diesen Bereich mit Ihrem DSM-Passwort, wenn Sie den Hintergrundzugriff aktivieren.",
|
|
43
|
+
"scopeMismatch": "Die Vorauswahl bei der Installation weicht vom bestätigten Bereich ab. Speichervorgänge im Hintergrund folgen dem bestätigten Bereich.",
|
|
44
|
+
"scopeChangeHelp": "Eine Änderung des Bereichs widerruft vorhandene Ziele und bricht ausstehende Speichervorgänge ab. Benutzer müssen ihre Ziele neu auswählen. Bereits gespeicherte Dateien bleiben erhalten.",
|
|
45
|
+
"confirmScope": "Bereichsänderung bestätigen"
|
|
46
|
+
}
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
"setupHelp": "Approve this app once to let it complete file jobs while users are offline.",
|
|
6
6
|
"password": "DSM password",
|
|
7
7
|
"authorize": "Approve background file access",
|
|
8
|
+
"confirmShare": "Confirm share",
|
|
9
|
+
"scopeNeeds": "Share needs confirmation. Choose a share below and confirm it with your DSM password.",
|
|
8
10
|
"ready": "Background file access is ready.",
|
|
9
11
|
"needs": "An administrator must approve background file access.",
|
|
10
12
|
"blocked": "Background file access is unavailable. Ask an administrator to check the app setup.",
|
|
@@ -26,11 +28,19 @@
|
|
|
26
28
|
"setupError": "Approval could not be completed. Re-enter your DSM password and try again, or check the app setup.",
|
|
27
29
|
"denied": "Access was refused. Check your folder permissions and try again.",
|
|
28
30
|
"session": "Sign in to DSM again, then refresh.",
|
|
29
|
-
"size": "Downloads are limited to 64
|
|
31
|
+
"size": "Downloads are limited to 64 MB and 60 seconds.",
|
|
30
32
|
"queued": "Queued",
|
|
31
33
|
"running": "Downloading",
|
|
32
34
|
"succeeded": "Saved",
|
|
33
35
|
"failed": "Failed",
|
|
34
36
|
"canceled": "Canceled",
|
|
35
|
-
"interrupted": "Interrupted"
|
|
37
|
+
"interrupted": "Interrupted",
|
|
38
|
+
"homeScope": "Each user’s home folder",
|
|
39
|
+
"scopeLabel": "Background save scope",
|
|
40
|
+
"protectedScope": "Confirmed scope",
|
|
41
|
+
"preselection": "Installer pre-selection",
|
|
42
|
+
"preselectionHelp": "The installer pre-selection is a suggestion. Confirm this scope with your DSM password when enabling background access.",
|
|
43
|
+
"scopeMismatch": "The installer pre-selection differs from the confirmed scope. Background saves follow the confirmed scope.",
|
|
44
|
+
"scopeChangeHelp": "Changing the scope revokes existing destinations and cancels pending saves. Users must choose destinations again. Saved files remain.",
|
|
45
|
+
"confirmScope": "Confirm scope change"
|
|
36
46
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Acceso a archivos en segundo plano",
|
|
3
|
+
"help": "Las descargas continúan después de cerrar esta aplicación. Cada tarea solo puede escribir en la carpeta seleccionada, durante una hora como máximo. Los archivos existentes nunca se reemplazan.",
|
|
4
|
+
"setupTitle": "Configuración del administrador",
|
|
5
|
+
"setupHelp": "Apruebe esta aplicación una vez para que complete tareas de archivos mientras los usuarios están desconectados.",
|
|
6
|
+
"password": "Contraseña de DSM",
|
|
7
|
+
"authorize": "Aprobar el acceso a archivos en segundo plano",
|
|
8
|
+
"confirmShare": "Confirmar carpeta compartida",
|
|
9
|
+
"scopeNeeds": "La carpeta compartida debe confirmarse. Elija una carpeta compartida abajo y confírmela con su contraseña de DSM.",
|
|
10
|
+
"ready": "El acceso a archivos en segundo plano está listo.",
|
|
11
|
+
"needs": "Un administrador debe aprobar el acceso a archivos en segundo plano.",
|
|
12
|
+
"blocked": "El acceso a archivos en segundo plano no está disponible. Pida a un administrador que revise la configuración de la aplicación.",
|
|
13
|
+
"refresh": "Actualizar",
|
|
14
|
+
"revoke": "Revocar",
|
|
15
|
+
"cancel": "Cancelar tarea",
|
|
16
|
+
"noDelegations": "No tiene delegaciones activas.",
|
|
17
|
+
"delegations": "Mis delegaciones",
|
|
18
|
+
"expires": "Caducidad",
|
|
19
|
+
"downloads": "Mis descargas",
|
|
20
|
+
"noJobs": "No tiene tareas de descarga.",
|
|
21
|
+
"url": "URL de descarga",
|
|
22
|
+
"name": "Nombre de archivo",
|
|
23
|
+
"choose": "Elegir carpeta",
|
|
24
|
+
"submit": "Descargar en mi carpeta",
|
|
25
|
+
"destination": "Destino",
|
|
26
|
+
"pending": "Tarea cancelada. La revocación está pendiente; la aplicación volverá a intentarlo.",
|
|
27
|
+
"error": "No se pudo completar la acción. Actualice e inténtelo de nuevo. Si sigue fallando, póngase en contacto con su administrador.",
|
|
28
|
+
"setupError": "No se pudo completar la aprobación. Vuelva a introducir su contraseña de DSM e inténtelo de nuevo, o revise la configuración de la aplicación.",
|
|
29
|
+
"denied": "Se denegó el acceso. Compruebe los permisos de la carpeta e inténtelo de nuevo.",
|
|
30
|
+
"session": "Vuelva a iniciar sesión en DSM y luego actualice.",
|
|
31
|
+
"size": "Las descargas están limitadas a 64 MB y 60 segundos.",
|
|
32
|
+
"queued": "En cola",
|
|
33
|
+
"running": "Descargando",
|
|
34
|
+
"succeeded": "Guardado",
|
|
35
|
+
"failed": "Error",
|
|
36
|
+
"canceled": "Cancelado",
|
|
37
|
+
"interrupted": "Interrumpido",
|
|
38
|
+
"homeScope": "Carpeta home de cada usuario",
|
|
39
|
+
"scopeLabel": "Ámbito de guardado en segundo plano",
|
|
40
|
+
"protectedScope": "Ámbito confirmado",
|
|
41
|
+
"preselection": "Preselección de la instalación",
|
|
42
|
+
"preselectionHelp": "La preselección de la instalación es solo una sugerencia. Confirme este ámbito con su contraseña de DSM al activar el acceso en segundo plano.",
|
|
43
|
+
"scopeMismatch": "La preselección de la instalación difiere del ámbito confirmado. Los guardados en segundo plano siguen el ámbito confirmado.",
|
|
44
|
+
"scopeChangeHelp": "Cambiar el ámbito revoca los destinos existentes y cancela los guardados pendientes. Los usuarios deben elegir de nuevo sus destinos. Los archivos ya guardados se conservan.",
|
|
45
|
+
"confirmScope": "Confirmar cambio de ámbito"
|
|
46
|
+
}
|