@synopackageland/templates 0.42.1 → 0.43.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.
Files changed (107) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +30 -16
  3. package/package.json +2 -2
  4. package/templates/_shared/AGENTS.md +8 -2
  5. package/templates/_shared/package.json +1 -1
  6. package/templates/_shared/pnpm-workspace.yaml +1 -0
  7. package/templates/compose/dsm-package.yaml +1 -1
  8. package/templates/native-go/README.md +7 -1
  9. package/templates/native-go/backend/THIRD_PARTY_NOTICES.txt +1 -1
  10. package/templates/native-go/backend/go.mod +1 -1
  11. package/templates/native-go/backend/go.sum +2 -2
  12. package/templates/native-go/i18n/glossary.json +4 -4
  13. package/templates/native-go/i18n/messages/de.json +1 -1
  14. package/templates/native-go/i18n/messages/en.json +1 -1
  15. package/templates/native-go/i18n/messages/es.json +1 -1
  16. package/templates/native-go/i18n/messages/fr.json +1 -1
  17. package/templates/native-go/i18n/messages/it.json +1 -1
  18. package/templates/native-go/i18n/messages/ja.json +1 -1
  19. package/templates/native-go/i18n/messages/ko.json +1 -1
  20. package/templates/native-go/i18n/messages/zh-CN.json +1 -1
  21. package/templates/native-go/i18n/messages/zh-TW.json +1 -1
  22. package/templates/native-go/ui/app.css +2 -1
  23. package/templates/native-go/ui/app.js +8 -4
  24. package/templates/native-node/README.md +1 -1
  25. package/templates/native-node/dsm-package.yaml +1 -1
  26. package/templates/native-python/dsm-package.yaml +1 -1
  27. package/templates/native-rust/README.md +3 -0
  28. package/templates/native-rust/backend/Cargo.lock +8 -8
  29. package/templates/native-rust/backend/Cargo.toml +1 -1
  30. package/templates/{native-go-delegate → overlays/native-go-delegate}/frontend/test/capability-panels.test.ts +6 -3
  31. package/templates/overlays/native-go-jobs/frontend/test/background-shell.test.ts +37 -0
  32. package/templates/{native-go-vue → overlays/native-go-vue}/README.md +16 -6
  33. package/templates/{native-go-vue → overlays/native-go-vue}/frontend/DESIGN.md +7 -3
  34. package/templates/{native-go-vue → overlays/native-go-vue}/frontend/package.json +2 -2
  35. package/templates/{native-go-vue → overlays/native-go-vue}/frontend/src/App.vue +7 -4
  36. package/templates/{native-go-vue → overlays/native-go-vue}/frontend/src/i18n/de.json +4 -7
  37. package/templates/{native-go-vue → overlays/native-go-vue}/frontend/src/i18n/en.json +4 -7
  38. package/templates/{native-go-vue → overlays/native-go-vue}/frontend/src/i18n/es.json +4 -7
  39. package/templates/{native-go-vue → overlays/native-go-vue}/frontend/src/i18n/fr.json +4 -7
  40. package/templates/{native-go-vue → overlays/native-go-vue}/frontend/src/i18n/it.json +4 -7
  41. package/templates/{native-go-vue → overlays/native-go-vue}/frontend/src/i18n/ja.json +4 -7
  42. package/templates/{native-go-vue → overlays/native-go-vue}/frontend/src/i18n/ko.json +4 -7
  43. package/templates/{native-go-vue → overlays/native-go-vue}/frontend/src/i18n/zh-CN.json +4 -7
  44. package/templates/{native-go-vue → overlays/native-go-vue}/frontend/src/i18n/zh-TW.json +4 -7
  45. package/templates/{native-go-vue → overlays/native-go-vue}/frontend/test/apps.test.ts +49 -7
  46. package/templates/{native-go-vue → overlays/native-go-vue}/frontend/test/shell-i18n.test.ts +6 -4
  47. package/templates/{native-node-vue → overlays/native-node-vue}/README.md +12 -4
  48. package/templates/{native-python-vue → overlays/native-python-vue}/README.md +12 -4
  49. package/templates/{native-rust-delegate → overlays/native-rust-delegate}/backend/Cargo.lock +8 -8
  50. package/templates/{native-rust-delegate → overlays/native-rust-delegate}/backend/Cargo.toml +1 -1
  51. package/templates/{native-rust-vue → overlays/native-rust-vue}/README.md +12 -4
  52. package/templates/_shared/pnpm-lock.yaml +0 -1636
  53. package/templates/native-go-jobs/frontend/test/background-shell.test.ts +0 -19
  54. package/templates/native-go-vue/frontend/pnpm-lock.yaml +0 -2269
  55. package/templates/native-node/backend/pnpm-lock.yaml +0 -207
  56. /package/templates/{native-go-delegate → overlays/native-go-delegate}/backend/cmd/background-worker/main.go +0 -0
  57. /package/templates/{native-go-delegate → overlays/native-go-delegate}/backend/internal/app/delegate.go +0 -0
  58. /package/templates/{native-go-delegate → overlays/native-go-delegate}/backend/internal/app/delegate_on.go +0 -0
  59. /package/templates/{native-go-delegate → overlays/native-go-delegate}/backend/internal/app/delegate_routes_test.go +0 -0
  60. /package/templates/{native-go-delegate → overlays/native-go-delegate}/frontend/src/CapabilityPanels.vue +0 -0
  61. /package/templates/{native-go-jobs → overlays/native-go-jobs}/backend/internal/app/background_on.go +0 -0
  62. /package/templates/{native-go-jobs → overlays/native-go-jobs}/backend/internal/app/background_routes_test.go +0 -0
  63. /package/templates/{native-go-root-ops → overlays/native-go-root-ops}/backend/internal/app/executor.go +0 -0
  64. /package/templates/{native-go-root-ops → overlays/native-go-root-ops}/backend/internal/app/executor_test.go +0 -0
  65. /package/templates/{native-go-root-ops → overlays/native-go-root-ops}/frontend/src/ExecutorGate.vue +0 -0
  66. /package/templates/{native-go-root-ops → overlays/native-go-root-ops}/frontend/test/executor-gate.test.ts +0 -0
  67. /package/templates/{native-go-root-ops → overlays/native-go-root-ops}/root/whoami.sh +0 -0
  68. /package/templates/{native-go-vue → overlays/native-go-vue}/frontend/pnpm-workspace.yaml +0 -0
  69. /package/templates/{native-go-vue → overlays/native-go-vue}/frontend/src/CapabilityPanels.vue +0 -0
  70. /package/templates/{native-go-vue → overlays/native-go-vue}/frontend/src/ExecutorGate.vue +0 -0
  71. /package/templates/{native-go-vue → overlays/native-go-vue}/frontend/src/i18n.ts +0 -0
  72. /package/templates/{native-go-vue → overlays/native-go-vue}/frontend/src/main.ts +0 -0
  73. /package/templates/{native-go-vue → overlays/native-go-vue}/frontend/src/style.css +0 -0
  74. /package/templates/{native-go-vue → overlays/native-go-vue}/frontend/test/context.ts +0 -0
  75. /package/templates/{native-go-vue → overlays/native-go-vue}/frontend/test/module-bundle.test.ts +0 -0
  76. /package/templates/{native-go-vue → overlays/native-go-vue}/frontend/test/native-controls.test.ts +0 -0
  77. /package/templates/{native-go-vue → overlays/native-go-vue}/frontend/test/page-spacing.test.ts +0 -0
  78. /package/templates/{native-go-vue → overlays/native-go-vue}/frontend/tsconfig.json +0 -0
  79. /package/templates/{native-go-vue → overlays/native-go-vue}/frontend/vite.config.ts +0 -0
  80. /package/templates/{native-go-vue → overlays/native-go-vue}/gitignore +0 -0
  81. /package/templates/{native-node-delegate → overlays/native-node-delegate}/backend/src/delegate.ts +0 -0
  82. /package/templates/{native-node-delegate → overlays/native-node-delegate}/backend/test/delegate.test.ts +0 -0
  83. /package/templates/{native-node-delegate → overlays/native-node-delegate}/operations.patch.json +0 -0
  84. /package/templates/{native-node-jobs → overlays/native-node-jobs}/backend/src/jobs.ts +0 -0
  85. /package/templates/{native-node-jobs → overlays/native-node-jobs}/backend/test/jobs.test.ts +0 -0
  86. /package/templates/{native-node-jobs → overlays/native-node-jobs}/operations.patch.json +0 -0
  87. /package/templates/{native-node-root-ops → overlays/native-node-root-ops}/backend/src/root-ops.ts +0 -0
  88. /package/templates/{native-node-root-ops → overlays/native-node-root-ops}/backend/test/root-ops.test.ts +0 -0
  89. /package/templates/{native-node-root-ops → overlays/native-node-root-ops}/operations.patch.json +0 -0
  90. /package/templates/{native-node-vue → overlays/native-node-vue}/gitignore +0 -0
  91. /package/templates/{native-python-delegate → overlays/native-python-delegate}/backend/app/delegate.py +0 -0
  92. /package/templates/{native-python-delegate → overlays/native-python-delegate}/backend/tests/test_delegate.py +0 -0
  93. /package/templates/{native-python-delegate → overlays/native-python-delegate}/operations.patch.json +0 -0
  94. /package/templates/{native-python-jobs → overlays/native-python-jobs}/backend/app/jobs.py +0 -0
  95. /package/templates/{native-python-jobs → overlays/native-python-jobs}/backend/tests/test_jobs.py +0 -0
  96. /package/templates/{native-python-jobs → overlays/native-python-jobs}/operations.patch.json +0 -0
  97. /package/templates/{native-python-root-ops → overlays/native-python-root-ops}/backend/app/root_ops.py +0 -0
  98. /package/templates/{native-python-root-ops → overlays/native-python-root-ops}/backend/tests/test_root_ops.py +0 -0
  99. /package/templates/{native-python-root-ops → overlays/native-python-root-ops}/operations.patch.json +0 -0
  100. /package/templates/{native-python-vue → overlays/native-python-vue}/gitignore +0 -0
  101. /package/templates/{native-rust-delegate → overlays/native-rust-delegate}/backend/src/delegate.rs +0 -0
  102. /package/templates/{native-rust-delegate → overlays/native-rust-delegate}/operations.patch.json +0 -0
  103. /package/templates/{native-rust-jobs → overlays/native-rust-jobs}/backend/src/jobs.rs +0 -0
  104. /package/templates/{native-rust-jobs → overlays/native-rust-jobs}/operations.patch.json +0 -0
  105. /package/templates/{native-rust-root-ops → overlays/native-rust-root-ops}/backend/src/root_ops.rs +0 -0
  106. /package/templates/{native-rust-root-ops → overlays/native-rust-root-ops}/operations.patch.json +0 -0
  107. /package/templates/{native-rust-vue → overlays/native-rust-vue}/gitignore +0 -0
@@ -46,13 +46,15 @@ async function refresh() {
46
46
  } catch(e) {if(!disposed && !(early && login.enabled.value)) error.value = message(e)}
47
47
  finally {if(!disposed) {busy.value=false; if(again) {again=false; void refresh()}}}
48
48
  }
49
- // The header Refresh also reloads the panels below and reads the portal again.
49
+ // No header Refresh: reopening the App, or showing its status page again, reads
50
+ // the status, the panels below and the portal again.
50
51
  const panels = ref<{refresh(): unknown}>()
51
52
  const portalReads = ref(0)
52
53
  const portalKey = computed(() => `${app.value?.user}:${portalReads.value}`)
53
54
  function refreshAll() {
54
55
  void refresh(); void panels.value?.refresh(); portalReads.value++
55
56
  }
57
+ watch(page, shown => {if(shown==='status') refreshAll()})
56
58
  watch(login.subject, (subject, previous) => {
57
59
  if(!subject) {app.value=undefined;error.value=''}
58
60
  else if((!previous || previous.user!==subject.user)) void refresh()
@@ -61,7 +63,7 @@ async function signOut() {
61
63
  try {await login.logout()} catch(e) {if(!disposed) error.value=message(e)}
62
64
  }
63
65
  onMounted(()=>{
64
- unsubscribe = props.ctx.reopen(()=>{if(!disposed) void refresh()})
66
+ unsubscribe = props.ctx.reopen(()=>{if(!disposed) refreshAll()})
65
67
  // The session check runs in the background; only a confirmed sign-in need replaces the App.
66
68
  void login.start()
67
69
  void refresh()
@@ -72,10 +74,11 @@ onUnmounted(()=>{disposed=true;unsubscribe?.();login.dispose()})
72
74
  <template>
73
75
  <AppShell app-root v-model="page" :ctx="shellCtx" :locale="ctx.locale" :pages="pages" :denied="signIn" sidebar-storage-key="__PACKAGE__">
74
76
  <template #denied><LoginPage :session="login" :locale="ctx.locale" /></template>
75
- <template #actions><Button variant="secondary" :disabled="busy" @click="refreshAll">{{ copy.refresh }}</Button><Button v-if="login.enabled.value" variant="secondary" :disabled="busy" @click="signOut">{{ copy.signOut }}</Button></template>
77
+ <!-- #actions holds real page actions (such as Add), never a Refresh. Without one the shell draws no bar. -->
78
+ <template v-if="login.enabled.value" #actions><Button variant="secondary" :disabled="busy" @click="signOut">{{ copy.signOut }}</Button></template>
76
79
  <ExecutorGate :ctx="ctx" :admin="ctx.user?.isAdmin ?? !!app?.admin">
77
80
  <div class="integration-app" :aria-busy="busy" :aria-label="copy.settings">
78
- <Callout v-if="error" tone="danger" :title="copy.actionRequired"><span role="alert">{{ error }}</span></Callout>
81
+ <Callout v-if="error" tone="danger" :title="copy.actionRequired"><span role="alert">{{ error }}</span><template #actions><Button variant="secondary" :disabled="busy" @click="refreshAll">{{ copy.retry }}</Button></template></Callout>
79
82
  <div data-page="status" v-show="page==='status'">
80
83
  <SettingsSection :title="copy.statusTitle" :collapsible="false" :loading="busy && !app">
81
84
  <div v-if="app" data-app-status>
@@ -1,19 +1,16 @@
1
1
  {
2
- "refresh": "Aktualisieren",
2
+ "retry": "Neuer Versuch",
3
3
  "signOut": "Abmelden",
4
4
  "settings": "Paketeinstellungen",
5
5
  "actionRequired": "Aktion erforderlich",
6
6
  "statusTitle": "Paketstatus",
7
- "statusError": "Status nicht verfügbar. Aktualisieren Sie die Ansicht",
8
- "greeting": "Begrüßung",
9
- "save": "Speichern",
10
- "saved": "Begrüßung gespeichert",
7
+ "statusError": "Status nicht verfügbar",
11
8
  "errors": {
12
9
  "session_required": "Ihre DSM-Sitzung ist abgelaufen. Melden Sie sich erneut an",
13
10
  "admin_required": "Diese Aktion muss ein DSM-Administrator ausführen",
14
11
  "app_privilege_denied": "Die DSM-Anwendungsberechtigungen erlauben diesem Konto nicht, diese App zu verwenden",
15
- "auth_unavailable": "Die DSM-Authentifizierung ist nicht verfügbar. Aktualisieren Sie die Ansicht und versuchen Sie es erneut",
16
- "request_failed": "Anfrage fehlgeschlagen ({code}). Aktualisieren Sie die Ansicht und versuchen Sie es erneut"
12
+ "auth_unavailable": "Die DSM-Authentifizierung ist nicht verfügbar. Versuchen Sie es später erneut",
13
+ "request_failed": "Anfrage fehlgeschlagen ({code}). Versuchen Sie es erneut"
17
14
  },
18
15
  "packageName": "Paketname",
19
16
  "user": "Benutzer",
@@ -1,19 +1,16 @@
1
1
  {
2
- "refresh": "Refresh",
2
+ "retry": "Retry",
3
3
  "signOut": "Sign out",
4
4
  "settings": "Package settings",
5
5
  "actionRequired": "Action required",
6
6
  "statusTitle": "Package status",
7
- "statusError": "Unable to load status. Refresh to try again",
8
- "greeting": "Greeting",
9
- "save": "Save",
10
- "saved": "Greeting saved",
7
+ "statusError": "Unable to load status",
11
8
  "errors": {
12
9
  "session_required": "Your DSM session expired. Sign in again",
13
10
  "admin_required": "A DSM administrator must perform this action",
14
11
  "app_privilege_denied": "DSM Application Privileges do not allow this account to use this app",
15
- "auth_unavailable": "DSM authentication is unavailable. Refresh to try again",
16
- "request_failed": "Request failed ({code}). Refresh and try again"
12
+ "auth_unavailable": "DSM authentication is unavailable. Try again later",
13
+ "request_failed": "Request failed ({code}). Try again"
17
14
  },
18
15
  "packageName": "Package name",
19
16
  "user": "User",
@@ -1,19 +1,16 @@
1
1
  {
2
- "refresh": "Actualizar",
2
+ "retry": "Volver a intentar",
3
3
  "signOut": "Cerrar sesión",
4
4
  "settings": "Configuración del paquete",
5
5
  "actionRequired": "Acción necesaria",
6
6
  "statusTitle": "Estado del paquete",
7
- "statusError": "Estado no disponible. Actualice la página",
8
- "greeting": "Saludo",
9
- "save": "Guardar",
10
- "saved": "Saludo guardado",
7
+ "statusError": "Estado no disponible",
11
8
  "errors": {
12
9
  "session_required": "Su sesión de DSM ha caducado. Vuelva a iniciar sesión",
13
10
  "admin_required": "Un administrador de DSM debe realizar esta acción",
14
11
  "app_privilege_denied": "Los privilegios de aplicación de DSM no permiten que esta cuenta use esta aplicación",
15
- "auth_unavailable": "La autenticación de DSM no está disponible. Actualice e inténtelo de nuevo",
16
- "request_failed": "La solicitud falló ({code}). Actualice e inténtelo de nuevo"
12
+ "auth_unavailable": "La autenticación de DSM no está disponible. Inténtelo de nuevo más tarde",
13
+ "request_failed": "La solicitud falló ({code}). Inténtelo de nuevo"
17
14
  },
18
15
  "packageName": "Nombre del paquete",
19
16
  "user": "Usuario",
@@ -1,19 +1,16 @@
1
1
  {
2
- "refresh": "Actualiser",
2
+ "retry": "Réessayer",
3
3
  "signOut": "Se déconnecter",
4
4
  "settings": "Paramètres du paquet",
5
5
  "actionRequired": "Action requise",
6
6
  "statusTitle": "État du paquet",
7
- "statusError": "État indisponible. Actualisez la page",
8
- "greeting": "Message d’accueil",
9
- "save": "Sauvegarder",
10
- "saved": "Message d’accueil enregistré",
7
+ "statusError": "État indisponible",
11
8
  "errors": {
12
9
  "session_required": "Votre session DSM a expiré. Reconnectez-vous",
13
10
  "admin_required": "Un administrateur DSM doit effectuer cette action",
14
11
  "app_privilege_denied": "Les privilèges d’application DSM n’autorisent pas ce compte à utiliser cette application",
15
- "auth_unavailable": "L’authentification DSM n’est pas disponible. Actualisez et réessayez",
16
- "request_failed": "La requête a échoué ({code}). Actualisez et réessayez"
12
+ "auth_unavailable": "L’authentification DSM n’est pas disponible. Réessayez plus tard",
13
+ "request_failed": "La requête a échoué ({code}). Réessayez"
17
14
  },
18
15
  "packageName": "Nom du paquet",
19
16
  "user": "Utilisateur",
@@ -1,19 +1,16 @@
1
1
  {
2
- "refresh": "Aggiorna",
2
+ "retry": "Riprova",
3
3
  "signOut": "Disconnetti",
4
4
  "settings": "Impostazioni del pacchetto",
5
5
  "actionRequired": "Azione richiesta",
6
6
  "statusTitle": "Stato del pacchetto",
7
- "statusError": "Stato non disponibile. Aggiornare la pagina",
8
- "greeting": "Saluto",
9
- "save": "Salva",
10
- "saved": "Saluto salvato",
7
+ "statusError": "Stato non disponibile",
11
8
  "errors": {
12
9
  "session_required": "La sessione DSM è scaduta. Accedere di nuovo",
13
10
  "admin_required": "Questa azione deve essere eseguita da un amministratore DSM",
14
11
  "app_privilege_denied": "I privilegi delle applicazioni DSM non consentono a questo account di usare questa app",
15
- "auth_unavailable": "L’autenticazione DSM non è disponibile. Aggiornare e riprovare",
16
- "request_failed": "Richiesta non riuscita ({code}). Aggiornare e riprovare"
12
+ "auth_unavailable": "L’autenticazione DSM non è disponibile. Riprovare più tardi",
13
+ "request_failed": "Richiesta non riuscita ({code}). Riprovare"
17
14
  },
18
15
  "packageName": "Nome pacchetto",
19
16
  "user": "Utente",
@@ -1,19 +1,16 @@
1
1
  {
2
- "refresh": "最新の情報に更新",
2
+ "retry": "再試行",
3
3
  "signOut": "サインアウト",
4
4
  "settings": "パッケージ設定",
5
5
  "actionRequired": "対応が必要です",
6
6
  "statusTitle": "パッケージの状態",
7
- "statusError": "ステータスを読み込めません。更新してください",
8
- "greeting": "あいさつ",
9
- "save": "保存",
10
- "saved": "あいさつを保存しました",
7
+ "statusError": "ステータスを読み込めません",
11
8
  "errors": {
12
9
  "session_required": "DSM セッションの有効期限が切れました。もう一度サインインしてください",
13
10
  "admin_required": "この操作は DSM 管理者が行う必要があります",
14
11
  "app_privilege_denied": "DSM のアプリケーション権限により、このアカウントはこのアプリを使用できません",
15
- "auth_unavailable": "DSM 認証を利用できません。最新の情報に更新してから、もう一度お試しください",
16
- "request_failed": "リクエストに失敗しました({code})。最新の情報に更新してから、もう一度お試しください"
12
+ "auth_unavailable": "DSM 認証を利用できません。しばらくしてから、もう一度お試しください",
13
+ "request_failed": "リクエストに失敗しました({code})。もう一度お試しください"
17
14
  },
18
15
  "packageName": "パッケージ名",
19
16
  "user": "ユーザー",
@@ -1,19 +1,16 @@
1
1
  {
2
- "refresh": "새로 고침",
2
+ "retry": "재시도",
3
3
  "signOut": "로그아웃",
4
4
  "settings": "패키지 설정",
5
5
  "actionRequired": "조치 필요",
6
6
  "statusTitle": "패키지 상태",
7
- "statusError": "상태를 불러올 수 없습니다. 새로 고침하십시오",
8
- "greeting": "인사말",
9
- "save": "저장",
10
- "saved": "인사말이 저장되었습니다",
7
+ "statusError": "상태를 불러올 수 없습니다",
11
8
  "errors": {
12
9
  "session_required": "DSM 세션이 만료되었습니다. 다시 로그인하십시오",
13
10
  "admin_required": "이 작업은 DSM 관리자가 수행해야 합니다",
14
11
  "app_privilege_denied": "DSM 응용 프로그램 권한에서 이 계정의 앱 사용이 허용되지 않습니다",
15
- "auth_unavailable": "DSM 인증을 사용할 수 없습니다. 새로 고친 후 다시 시도하십시오",
16
- "request_failed": "요청이 실패했습니다({code}). 새로 고친 후 다시 시도하십시오"
12
+ "auth_unavailable": "DSM 인증을 사용할 수 없습니다. 잠시 후 다시 시도하십시오",
13
+ "request_failed": "요청이 실패했습니다({code}). 다시 시도하십시오"
17
14
  },
18
15
  "packageName": "패키지 이름",
19
16
  "user": "사용자",
@@ -1,19 +1,16 @@
1
1
  {
2
- "refresh": "刷新",
2
+ "retry": "重试",
3
3
  "signOut": "注销",
4
4
  "settings": "套件设置",
5
5
  "actionRequired": "需要处理",
6
6
  "statusTitle": "套件状态",
7
- "statusError": "无法加载状态,请刷新",
8
- "greeting": "问候语",
9
- "save": "保存",
10
- "saved": "问候语已保存",
7
+ "statusError": "无法加载状态",
11
8
  "errors": {
12
9
  "session_required": "DSM 登录已过期,请重新登录",
13
10
  "admin_required": "此操作必须由 DSM 管理员执行",
14
11
  "app_privilege_denied": "DSM 应用程序权限不允许此账户使用此 App",
15
- "auth_unavailable": "DSM 验证暂时不可用,请刷新后重试",
16
- "request_failed": "请求失败({code}),请刷新后重试"
12
+ "auth_unavailable": "DSM 验证暂时不可用,请稍后重试",
13
+ "request_failed": "请求失败({code}),请重试"
17
14
  },
18
15
  "packageName": "套件名称",
19
16
  "user": "用户帐号",
@@ -1,19 +1,16 @@
1
1
  {
2
- "refresh": "重新整理",
2
+ "retry": "重試",
3
3
  "signOut": "登出",
4
4
  "settings": "套件設定",
5
5
  "actionRequired": "需要處理",
6
6
  "statusTitle": "套件狀態",
7
- "statusError": "無法載入狀態,請重新整理",
8
- "greeting": "問候語",
9
- "save": "儲存",
10
- "saved": "問候語已儲存",
7
+ "statusError": "無法載入狀態",
11
8
  "errors": {
12
9
  "session_required": "DSM 登入已逾時,請重新登入",
13
10
  "admin_required": "此操作須由 DSM 管理員執行",
14
11
  "app_privilege_denied": "DSM 應用程式權限不允許此帳號使用這個 App",
15
- "auth_unavailable": "DSM 驗證目前無法使用,請重新整理後再試",
16
- "request_failed": "要求失敗({code}),請重新整理後再試"
12
+ "auth_unavailable": "DSM 驗證目前無法使用,請稍後再試",
13
+ "request_failed": "要求失敗({code}),請再試一次"
17
14
  },
18
15
  "packageName": "套件名稱",
19
16
  "user": "使用者帳號",
@@ -47,13 +47,26 @@ test('a DSM window stays light when the system prefers dark',async()=>{
47
47
  expect(f.root.textContent).toContain('Light')
48
48
  }finally{f.close();Object.defineProperty(window,'matchMedia',{configurable:true,writable:true,value:query})}
49
49
  })
50
- test('failed status loading offers a retry and releases the mounting class',async()=>{
50
+ test('failed status loading offers a Retry beside the error and releases the mounting class',async()=>{
51
51
  const f=fixture('Failure')
52
52
  try{
53
- await settle();f.call.mockRejectedValue({code:'auth_unavailable'});f.reopen.forEach(fn=>fn());await settle()
53
+ await settle();f.app.unmount()
54
+ let fail=true
55
+ f.call.mockImplementation(async(method:string)=>{
56
+ if(method!=='app.status')throw {code:'unknown_method'}
57
+ if(fail){fail=false;throw {code:'auth_unavailable'}}
58
+ return {package:'Failure',version:'1',user:'admin',admin:true}
59
+ })
60
+ const app=mount(f.root,f.ctx);await settle()
54
61
  expect(f.root.textContent).toContain('DSM authentication is unavailable')
55
- expect(f.root.querySelector('button')?.disabled).toBe(false)
56
- f.app.unmount();expect(f.root.classList.contains('dsmapp-vue-root')).toBe(false)
62
+ const retry=[...f.root.querySelectorAll<HTMLButtonElement>('[data-ui-callout-actions] button')].find(n=>n.textContent==='Retry')!
63
+ expect(retry.disabled).toBe(false)
64
+ const reads=f.call.mock.calls.filter(([method])=>method==='app.status').length
65
+ retry.click();await settle()
66
+ expect(f.call.mock.calls.filter(([method])=>method==='app.status').length).toBeGreaterThan(reads)
67
+ expect(f.root.textContent).not.toContain('DSM authentication is unavailable')
68
+ expect(f.root.querySelector('[data-app-status]')).not.toBeNull()
69
+ app.unmount();expect(f.root.classList.contains('dsmapp-vue-root')).toBe(false)
57
70
  }finally{f.close()}
58
71
  })
59
72
  test('a portal app shows the Application Portal section on the status page',async()=>{
@@ -70,7 +83,7 @@ test('a portal app shows the Application Portal section on the status page',asyn
70
83
  app.unmount()
71
84
  }finally{without.close();f.root.remove()}
72
85
  })
73
- test('the header Refresh reads the portal and executor status again',async()=>{
86
+ test('reopening the App reads the status, portal and executor status again',async()=>{
74
87
  const f=fixture('Portal')
75
88
  try{
76
89
  await settle();f.app.unmount()
@@ -79,13 +92,42 @@ test('the header Refresh reads the portal and executor status again',async()=>{
79
92
  f.ctx.portal={messages:{title:'Application Portal',link:'Direct link:'},status:portal,apply:vi.fn(),disable:vi.fn(),openControlPanel:vi.fn(),mountPanel:vi.fn()}
80
93
  f.ctx.executor={status:executor,authorize:vi.fn(),control:vi.fn()} as unknown as Context['executor']
81
94
  const app=mount(f.root,f.ctx);await settle()
82
- const [portalReads,executorReads]=[portal.mock.calls.length,executor.mock.calls.length]
83
- ;[...f.root.querySelectorAll('button')].find(n=>n.textContent==='Refresh')!.click();await settle()
95
+ const reads=()=>f.call.mock.calls.filter(([method])=>method==='app.status').length
96
+ const [statusReads,portalReads,executorReads]=[reads(),portal.mock.calls.length,executor.mock.calls.length]
97
+ f.reopen.forEach(fn=>fn());await settle()
98
+ expect(reads()).toBeGreaterThan(statusReads)
84
99
  expect(portal.mock.calls.length).toBeGreaterThan(portalReads)
85
100
  expect(executor.mock.calls.length).toBeGreaterThan(executorReads)
86
101
  app.unmount()
87
102
  }finally{f.root.remove()}
88
103
  })
104
+ // The template puts no Refresh in the shell's #actions: with nothing there the
105
+ // shell draws no bar above the page. Sign out shows only with standalone login.
106
+ test('the status page has no header bar and no Refresh button',async()=>{
107
+ const f=fixture('Bare')
108
+ try{
109
+ await settle()
110
+ expect([...f.root.querySelectorAll('button')].some(n=>n.textContent?.trim()==='Refresh')).toBe(false)
111
+ const bar=f.root.querySelector<HTMLElement>('[data-app-page-bar]')
112
+ expect(bar===null||bar.hidden).toBe(true)
113
+ }finally{f.close()}
114
+ })
115
+ test('with standalone login the bar holds only Sign out',async()=>{
116
+ const call=vi.fn(async(method:string)=>{
117
+ if(method==='identity.status')return {enabled:true}
118
+ if(method==='identity.session')return {user:'alice',uid:'1',expiresAt:new Date(Date.now()+3600_000).toISOString()}
119
+ if(method==='app.status')return {package:'Login',version:'1',user:'alice',admin:false}
120
+ throw {code:'unknown_method'}
121
+ })
122
+ const root=document.createElement('div');document.body.append(root)
123
+ const app=mount(root,dsmWindow({call}))
124
+ try{
125
+ await settle()
126
+ const bar=root.querySelector<HTMLElement>('[data-app-page-bar]')!
127
+ expect(bar.hidden).toBe(false)
128
+ expect([...bar.querySelectorAll('button')].map(n=>n.textContent?.trim())).toEqual(['Sign out'])
129
+ }finally{app.unmount();root.remove()}
130
+ })
89
131
  test('the app renders and loads status while the session check is still pending',async()=>{
90
132
  const call=vi.fn(async(method:string)=>{
91
133
  if(method==='identity.status')return new Promise(()=>{})
@@ -8,8 +8,10 @@ import {appCopy} from '../src/i18n'
8
8
  import {identityCopy} from '@synopackageland/sdk/vue'
9
9
  async function settle(){for(let i=0;i<20;i++){await Promise.resolve();await nextTick()}}
10
10
  const buttons=(root:HTMLElement)=>[...root.querySelectorAll('button')].map(b=>b.textContent?.trim())
11
+ const reopen:Array<()=>void>=[]
11
12
  function start(locale:string,call:(method:string)=>Promise<unknown>){
12
- const ctx=dsmWindow({locale,call:vi.fn(call) as never})
13
+ reopen.length=0
14
+ const ctx=dsmWindow({locale,call:vi.fn(call) as never,reopen(fn:()=>void){reopen.push(fn);return ()=>{}}})
13
15
  const root=document.createElement('div');document.body.append(root)
14
16
  const app=mount(root,ctx)
15
17
  return {root,close(){app.unmount();root.remove()}}
@@ -23,12 +25,12 @@ test('the status page and its errors follow the DSM language',async()=>{
23
25
  })
24
26
  try{
25
27
  await settle()
26
- expect(buttons(root)).toContain(copy.refresh)
28
+ expect(buttons(root)).not.toContain('Aktualisieren')
27
29
  expect(root.textContent).toContain(copy.statusTitle)
28
30
  expect(root.textContent).toContain(copy.packageName)
29
- expect(root.textContent).not.toContain(copy.greeting)
30
- denied=true;[...root.querySelectorAll('button')].find(button=>button.textContent?.trim()===copy.refresh)!.click();await settle()
31
+ denied=true;reopen.forEach(fn=>fn());await settle()
31
32
  expect(root.textContent).toContain(copy.errors.admin_required)
33
+ expect(buttons(root)).toContain(copy.retry)
32
34
  expect(root.querySelector('[aria-label]')?.getAttribute('aria-label')).not.toBe('Package settings')
33
35
  }finally{close()}
34
36
  })
@@ -26,7 +26,7 @@ run `synopkgland ctl generate`.
26
26
 
27
27
  ```sh
28
28
  cd frontend
29
- pnpm install --frozen-lockfile --prod=false
29
+ pnpm install --prod=false
30
30
  pnpm test
31
31
  pnpm run build
32
32
  ```
@@ -45,7 +45,7 @@ bridge App's runtime serves.
45
45
 
46
46
  Use the installed `@synopackageland/ui` and its declared compatible patch range.
47
47
  `App.vue` lays the shell out with the SDK's `AppShell` (`@synopackageland/sdk/vue`):
48
- `pages` from `shellPages`, the page's buttons in `#actions` and the pages in its
48
+ `pages` from `shellPages`, real page actions in `#actions` and the pages in its
49
49
  default slot. On desktop, the sidebar names the page: the shell shows no page title, puts
50
50
  `#actions` in a bar above the page and scrolls only the content; a settings
51
51
  page (`SectionTabs fill` or `SettingsPage`) keeps its tabs and footer in place
@@ -60,6 +60,14 @@ device-width viewport. Its full-page sidebar names the App with
60
60
  shows the signed-in DSM account and its role from `ctx.user`. See the ui's
61
61
  `docs/adaptive-layout.md`.
62
62
 
63
+ The template puts no Refresh in `#actions`, and with nothing there `AppShell`
64
+ draws no bar above the page. Data stays fresh without one: `ctx.reopen` reads
65
+ the status, the panels and the portal again when the window is shown again,
66
+ and showing the status page again reads it again. A failed read offers Retry
67
+ beside its error. Sign out appears in `#actions` only when standalone login is
68
+ on. Keep `#actions` for real page actions, such as Add; a section that needs a
69
+ manual re-read gets its own action in that section, not a header Refresh.
70
+
63
71
  The frontend SDK and UI use patch ranges; the lockfile records the installed
64
72
  versions. Update the lockfile deliberately when taking a newer patch.
65
73
 
@@ -71,8 +79,8 @@ keeps that branding, and the UI hides a bar with nothing in it. When using
71
79
  even with `phone-bar-title="none"`. Pages outside the tab bar and detail pages
72
80
  with Back keep their title bar. `header-actions` share one "…" menu; only an
73
81
  entry marked `primary: true`, or a sole entry with an icon, becomes a separate
74
- bar button. The template's `#actions` stays page actions; no title or empty-bar
75
- workaround is needed.
82
+ bar button. The template's `#actions` holds only real page actions; no title or
83
+ empty-bar workaround is needed.
76
84
 
77
85
  `AppShell`'s `app-root` wraps it in an app-local `AppRoot` (app modal scope, on
78
86
  a full page as well, bounded at `height: 100%`) with the UI locale from
@@ -26,7 +26,7 @@ run `synopkgland ctl generate`.
26
26
 
27
27
  ```sh
28
28
  cd frontend
29
- pnpm install --frozen-lockfile --prod=false
29
+ pnpm install --prod=false
30
30
  pnpm test
31
31
  pnpm run build
32
32
  ```
@@ -45,7 +45,7 @@ bridge App's runtime serves.
45
45
 
46
46
  Use the installed `@synopackageland/ui` and its declared compatible patch range.
47
47
  `App.vue` lays the shell out with the SDK's `AppShell` (`@synopackageland/sdk/vue`):
48
- `pages` from `shellPages`, the page's buttons in `#actions` and the pages in its
48
+ `pages` from `shellPages`, real page actions in `#actions` and the pages in its
49
49
  default slot. On desktop, the sidebar names the page: the shell shows no page title, puts
50
50
  `#actions` in a bar above the page and scrolls only the content; a settings
51
51
  page (`SectionTabs fill` or `SettingsPage`) keeps its tabs and footer in place
@@ -60,6 +60,14 @@ device-width viewport. Its full-page sidebar names the App with
60
60
  shows the signed-in DSM account and its role from `ctx.user`. See the ui's
61
61
  `docs/adaptive-layout.md`.
62
62
 
63
+ The template puts no Refresh in `#actions`, and with nothing there `AppShell`
64
+ draws no bar above the page. Data stays fresh without one: `ctx.reopen` reads
65
+ the status, the panels and the portal again when the window is shown again,
66
+ and showing the status page again reads it again. A failed read offers Retry
67
+ beside its error. Sign out appears in `#actions` only when standalone login is
68
+ on. Keep `#actions` for real page actions, such as Add; a section that needs a
69
+ manual re-read gets its own action in that section, not a header Refresh.
70
+
63
71
  The frontend SDK and UI use patch ranges; the lockfile records the installed
64
72
  versions. Update the lockfile deliberately when taking a newer patch.
65
73
 
@@ -71,8 +79,8 @@ keeps that branding, and the UI hides a bar with nothing in it. When using
71
79
  even with `phone-bar-title="none"`. Pages outside the tab bar and detail pages
72
80
  with Back keep their title bar. `header-actions` share one "…" menu; only an
73
81
  entry marked `primary: true`, or a sole entry with an icon, becomes a separate
74
- bar button. The template's `#actions` stays page actions; no title or empty-bar
75
- workaround is needed.
82
+ bar button. The template's `#actions` holds only real page actions; no title or
83
+ empty-bar workaround is needed.
76
84
 
77
85
  `AppShell`'s `app-root` wraps it in an app-local `AppRoot` (app modal scope, on
78
86
  a full page as well, bounded at `height: 100%`) with the UI locale from
@@ -69,15 +69,15 @@ dependencies = [
69
69
 
70
70
  [[package]]
71
71
  name = "itoa"
72
- version = "1.0.18"
72
+ version = "1.0.19"
73
73
  source = "registry+https://github.com/rust-lang/crates.io-index"
74
- checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
74
+ checksum = "3bd1574e617a9a6c1f8f8181bd7e841e1f20e4277607ef28945765188d6284d4"
75
75
 
76
76
  [[package]]
77
77
  name = "libc"
78
- version = "0.2.190"
78
+ version = "0.2.191"
79
79
  source = "registry+https://github.com/rust-lang/crates.io-index"
80
- checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
80
+ checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
81
81
 
82
82
  [[package]]
83
83
  name = "memchr"
@@ -159,9 +159,9 @@ dependencies = [
159
159
 
160
160
  [[package]]
161
161
  name = "syn"
162
- version = "3.0.9"
162
+ version = "3.0.10"
163
163
  source = "registry+https://github.com/rust-lang/crates.io-index"
164
- checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
164
+ checksum = "4ae9ccbea3a7b0b09443036490c8f34a8b1b760fd345e5c0dae0aac718faea79"
165
165
  dependencies = [
166
166
  "proc-macro2",
167
167
  "quote",
@@ -170,8 +170,8 @@ dependencies = [
170
170
 
171
171
  [[package]]
172
172
  name = "syno-pkg-land-app"
173
- version = "0.53.16"
174
- source = "git+https://github.com/synopackageland/syno-pkg-land-sdk?tag=v0.53.16#6751951c140fb032e5f050dc53f5cc58fcd0bf60"
173
+ version = "0.53.42"
174
+ source = "git+https://github.com/synopackageland/syno-pkg-land-sdk?tag=v0.53.42#02d5b526c852b3bd30e4dc9031414c9907fadb57"
175
175
  dependencies = [
176
176
  "libc",
177
177
  "serde",
@@ -10,7 +10,7 @@ libc = "0.2"
10
10
  serde = { version = "1", features = ["derive"] }
11
11
  serde_json = "1"
12
12
  # The SDK version is the tag; `synopkgland check` reads it from Cargo.lock.
13
- syno-pkg-land-app = { git = "https://github.com/synopackageland/syno-pkg-land-sdk", tag = "v0.53.16" }
13
+ syno-pkg-land-app = { git = "https://github.com/synopackageland/syno-pkg-land-sdk", tag = "v0.53.42" }
14
14
 
15
15
  [profile.release]
16
16
  # A handler panic becomes an "internal" reply; abort would end the App without one.
@@ -26,7 +26,7 @@ run `synopkgland ctl generate`.
26
26
 
27
27
  ```sh
28
28
  cd frontend
29
- pnpm install --frozen-lockfile --prod=false
29
+ pnpm install --prod=false
30
30
  pnpm test
31
31
  pnpm run build
32
32
  ```
@@ -45,7 +45,7 @@ bridge App's runtime serves.
45
45
 
46
46
  Use the installed `@synopackageland/ui` and its declared compatible patch range.
47
47
  `App.vue` lays the shell out with the SDK's `AppShell` (`@synopackageland/sdk/vue`):
48
- `pages` from `shellPages`, the page's buttons in `#actions` and the pages in its
48
+ `pages` from `shellPages`, real page actions in `#actions` and the pages in its
49
49
  default slot. On desktop, the sidebar names the page: the shell shows no page title, puts
50
50
  `#actions` in a bar above the page and scrolls only the content; a settings
51
51
  page (`SectionTabs fill` or `SettingsPage`) keeps its tabs and footer in place
@@ -60,6 +60,14 @@ device-width viewport. Its full-page sidebar names the App with
60
60
  shows the signed-in DSM account and its role from `ctx.user`. See the ui's
61
61
  `docs/adaptive-layout.md`.
62
62
 
63
+ The template puts no Refresh in `#actions`, and with nothing there `AppShell`
64
+ draws no bar above the page. Data stays fresh without one: `ctx.reopen` reads
65
+ the status, the panels and the portal again when the window is shown again,
66
+ and showing the status page again reads it again. A failed read offers Retry
67
+ beside its error. Sign out appears in `#actions` only when standalone login is
68
+ on. Keep `#actions` for real page actions, such as Add; a section that needs a
69
+ manual re-read gets its own action in that section, not a header Refresh.
70
+
63
71
  The frontend SDK and UI use patch ranges; the lockfile records the installed
64
72
  versions. Update the lockfile deliberately when taking a newer patch.
65
73
 
@@ -71,8 +79,8 @@ keeps that branding, and the UI hides a bar with nothing in it. When using
71
79
  even with `phone-bar-title="none"`. Pages outside the tab bar and detail pages
72
80
  with Back keep their title bar. `header-actions` share one "…" menu; only an
73
81
  entry marked `primary: true`, or a sole entry with an icon, becomes a separate
74
- bar button. The template's `#actions` stays page actions; no title or empty-bar
75
- workaround is needed.
82
+ bar button. The template's `#actions` holds only real page actions; no title or
83
+ empty-bar workaround is needed.
76
84
 
77
85
  `AppShell`'s `app-root` wraps it in an app-local `AppRoot` (app modal scope, on
78
86
  a full page as well, bounded at `height: 100%`) with the UI locale from