@synopackageland/templates 0.9.7 → 0.9.9

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 (50) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/templates/native-go/backend/THIRD_PARTY_NOTICES.txt +1 -1
  4. package/templates/native-go/backend/go.mod +1 -1
  5. package/templates/native-go/backend/go.sum +2 -2
  6. package/templates/native-go/backend/internal/app/background_test.go +1 -0
  7. package/templates/native-go/backend/internal/app/routes_test.go +2 -0
  8. package/templates/native-go/i18n/glossary.json +3 -1
  9. package/templates/native-go/i18n/messages/de.json +3 -0
  10. package/templates/native-go/i18n/messages/en.json +3 -0
  11. package/templates/native-go/i18n/messages/es.json +3 -0
  12. package/templates/native-go/i18n/messages/fr.json +3 -0
  13. package/templates/native-go/i18n/messages/it.json +3 -0
  14. package/templates/native-go/i18n/messages/ja.json +3 -0
  15. package/templates/native-go/i18n/messages/ko.json +3 -0
  16. package/templates/native-go/i18n/messages/zh-CN.json +3 -0
  17. package/templates/native-go/i18n/messages/zh-TW.json +3 -0
  18. package/templates/native-go-delegate/frontend/src/CapabilityPanels.vue +8 -6
  19. package/templates/native-go-delegate/frontend/test/capability-panels.test.ts +28 -2
  20. package/templates/native-go-jobs/backend/internal/app/background_routes_test.go +1 -0
  21. package/templates/native-go-jobs/frontend/test/background-page.test.ts +18 -0
  22. package/templates/native-go-vue/frontend/package-lock.json +12 -12
  23. package/templates/native-go-vue/frontend/package.json +2 -2
  24. package/templates/native-go-vue/frontend/src/App.vue +14 -7
  25. package/templates/native-go-vue/frontend/src/CapabilityPanels.vue +4 -1
  26. package/templates/native-go-vue/frontend/src/i18n/de.json +0 -1
  27. package/templates/native-go-vue/frontend/src/i18n/en.json +0 -1
  28. package/templates/native-go-vue/frontend/src/i18n/es.json +0 -1
  29. package/templates/native-go-vue/frontend/src/i18n/fr.json +0 -1
  30. package/templates/native-go-vue/frontend/src/i18n/it.json +0 -1
  31. package/templates/native-go-vue/frontend/src/i18n/ja.json +0 -1
  32. package/templates/native-go-vue/frontend/src/i18n/ko.json +0 -1
  33. package/templates/native-go-vue/frontend/src/i18n/zh-CN.json +0 -1
  34. package/templates/native-go-vue/frontend/src/i18n/zh-TW.json +0 -1
  35. package/templates/native-go-vue/frontend/test/apps.test.ts +37 -0
  36. package/templates/native-go-vue/frontend/test/native-controls.test.ts +4 -4
  37. package/templates/native-rust/backend/Cargo.lock +2 -2
  38. package/templates/native-rust/backend/Cargo.toml +1 -1
  39. package/templates/native-rust/backend/THIRD_PARTY_NOTICES.txt +1 -1
  40. package/templates/native-rust/backend/src/app/test_support.rs +2 -0
  41. package/templates/native-rust/i18n/glossary.json +3 -1
  42. package/templates/native-rust/i18n/messages/de.json +3 -0
  43. package/templates/native-rust/i18n/messages/en.json +3 -0
  44. package/templates/native-rust/i18n/messages/es.json +3 -0
  45. package/templates/native-rust/i18n/messages/fr.json +3 -0
  46. package/templates/native-rust/i18n/messages/it.json +3 -0
  47. package/templates/native-rust/i18n/messages/ja.json +3 -0
  48. package/templates/native-rust/i18n/messages/ko.json +3 -0
  49. package/templates/native-rust/i18n/messages/zh-CN.json +3 -0
  50. package/templates/native-rust/i18n/messages/zh-TW.json +3 -0
package/README.md CHANGED
@@ -36,7 +36,7 @@ Vue shell from `native-go-vue` (then `native-rust-vue`), `frontend/` of
36
36
  `native-rust-root-ops`'s `root_routes.rs`, the executor shell Go borrows as
37
37
  `executor.go`. `scripts/assemble.sh --language rust` does exactly this.
38
38
 
39
- `backend/go.mod` requires `github.com/synopackageland/syno-pkg-land-sdk v0.9.8`; that line is
39
+ `backend/go.mod` requires `github.com/synopackageland/syno-pkg-land-sdk v0.9.9`; that line is
40
40
  the single source of the SDK version and `frontend/package.json` pins
41
41
  `@synopackageland/sdk` to the same version. A Rust project takes it from the
42
42
  `tag` of the `syno-pkg-land-sdk` git dependency in `backend/Cargo.toml`, which
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synopackageland/templates",
3
- "version": "0.9.7",
3
+ "version": "0.9.9",
4
4
  "description": "Project templates for synopkgland init: native-go and native-rust (plain or Vue, with jobs, delegate and root-ops overlays), binary and compose.",
5
5
  "license": "MIT",
6
6
  "repository": {"type": "git", "url": "git+https://github.com/synopackageland/syno-pkg-land-templates.git"},
@@ -1,6 +1,6 @@
1
1
  Native Go package build dependencies
2
2
 
3
- github.com/synopackageland/syno-pkg-land-sdk v0.9.8 (MIT)
3
+ github.com/synopackageland/syno-pkg-land-sdk v0.9.9 (MIT)
4
4
 
5
5
  Copyright (c) 2026 synopkgland
6
6
 
@@ -4,6 +4,6 @@ go 1.25.0
4
4
 
5
5
  toolchain go1.26.9
6
6
 
7
- require github.com/synopackageland/syno-pkg-land-sdk v0.9.8
7
+ require github.com/synopackageland/syno-pkg-land-sdk v0.9.9
8
8
 
9
9
  require golang.org/x/sys v0.44.0 // indirect
@@ -1,4 +1,4 @@
1
- github.com/synopackageland/syno-pkg-land-sdk v0.9.8 h1:V59KMPcCcnwmQI10U0fDRIbCNhjJkysJ/HzdIr1kH+I=
2
- github.com/synopackageland/syno-pkg-land-sdk v0.9.8/go.mod h1:hlsvaVuH2wsAoR8jzI1Rg/NeejQSFpAaN6zpIWsVbjI=
1
+ github.com/synopackageland/syno-pkg-land-sdk v0.9.9 h1:TUsblZtIwlBO0DpBTUOj6gd8+xTi60Y0CwkKNb+OR3M=
2
+ github.com/synopackageland/syno-pkg-land-sdk v0.9.9/go.mod h1:hlsvaVuH2wsAoR8jzI1Rg/NeejQSFpAaN6zpIWsVbjI=
3
3
  golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
4
4
  golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
@@ -63,6 +63,7 @@ func (e *bgEnv) call(auth api.Authenticator, method, params string) (string, map
63
63
  }
64
64
  req := httptest.NewRequest(http.MethodPost, "/api.cgi", strings.NewReader(`{"method":"`+method+`","params":`+params+`}`))
65
65
  req.Header.Set("Content-Type", "application/json")
66
+ req.Header.Set("Sec-Fetch-Site", "same-origin")
66
67
  rec := httptest.NewRecorder()
67
68
  srv.ServeHTTP(rec, req)
68
69
  var out map[string]any
@@ -67,6 +67,8 @@ func apiRequest(method, params string) *http.Request {
67
67
  body := `{"method":"` + method + `","params":` + params + `}`
68
68
  req := httptest.NewRequest(http.MethodPost, "/api.cgi", strings.NewReader(body))
69
69
  req.Header.Set("Content-Type", "application/json")
70
+ // A browser says which page sent a POST; the SDK refuses one that does not.
71
+ req.Header.Set("Sec-Fetch-Site", "same-origin")
70
72
  return req
71
73
  }
72
74
 
@@ -624,7 +624,9 @@
624
624
  "error.method_not_allowed",
625
625
  "error.json_required",
626
626
  "error.cross_origin",
627
- "error.request_too_large"
627
+ "error.request_too_large",
628
+ "error.request_timeout",
629
+ "error.request_canceled"
628
630
  ]
629
631
  },
630
632
  {
@@ -8,11 +8,14 @@
8
8
  "error.bad_request": "Überprüfen Sie den eingegebenen Wert und versuchen Sie es erneut.",
9
9
  "error.bad_response": "Dieser Vorgang kann nicht ausgeführt werden. Bitte versuchen Sie es später erneut.",
10
10
  "error.cross_origin": "Dieser Vorgang kann nicht ausgeführt werden. Bitte versuchen Sie es später erneut.",
11
+ "error.origin_required": "Die Anfrage wurde abgelehnt: Der Browser hat nicht angegeben, dass sie von der Seite dieser App stammt. Öffnen Sie die App in einem aktuellen Browser erneut.",
11
12
  "error.internal": "Dieser Vorgang kann nicht ausgeführt werden. Bitte versuchen Sie es später erneut.",
12
13
  "error.json_required": "Dieser Vorgang kann nicht ausgeführt werden. Bitte versuchen Sie es später erneut.",
13
14
  "error.method_not_allowed": "Dieser Vorgang kann nicht ausgeführt werden. Bitte versuchen Sie es später erneut.",
14
15
  "error.network": "Verbindung fehlgeschlagen. Bitte überprüfen Sie Ihre Netzwerkeinstellungen.",
15
16
  "error.request_too_large": "Dieser Vorgang kann nicht ausgeführt werden. Bitte versuchen Sie es später erneut.",
17
+ "error.request_timeout": "Dieser Vorgang kann nicht ausgeführt werden. Bitte versuchen Sie es später erneut.",
18
+ "error.request_canceled": "Dieser Vorgang kann nicht ausgeführt werden. Bitte versuchen Sie es später erneut.",
16
19
  "error.session_required": "Verbindung abgelaufen. Melden Sie sich erneut an.",
17
20
  "error.unknown_method": "Dieser Vorgang kann nicht ausgeführt werden. Bitte versuchen Sie es später erneut.",
18
21
  "greeting.adminOnly": "Nur Mitglieder der Gruppe administrators können den Gruß ändern.",
@@ -8,11 +8,14 @@
8
8
  "error.bad_request": "Check the entered value and try again.",
9
9
  "error.bad_response": "Unable to perform this operation. Please try again later.",
10
10
  "error.cross_origin": "Unable to perform this operation. Please try again later.",
11
+ "error.origin_required": "The request was refused: the browser did not show that it came from this App's page. Open the App again in a current browser.",
11
12
  "error.internal": "Unable to perform this operation. Please try again later.",
12
13
  "error.json_required": "Unable to perform this operation. Please try again later.",
13
14
  "error.method_not_allowed": "Unable to perform this operation. Please try again later.",
14
15
  "error.network": "Connection failed. Please check your network settings.",
15
16
  "error.request_too_large": "Unable to perform this operation. Please try again later.",
17
+ "error.request_timeout": "Unable to perform this operation. Please try again later.",
18
+ "error.request_canceled": "Unable to perform this operation. Please try again later.",
16
19
  "error.session_required": "Connection expired. Please sign in again.",
17
20
  "error.unknown_method": "Unable to perform this operation. Please try again later.",
18
21
  "greeting.adminOnly": "Only members of the administrators group can change the greeting.",
@@ -8,11 +8,14 @@
8
8
  "error.bad_request": "Compruebe el valor introducido y vuelva a intentarlo.",
9
9
  "error.bad_response": "No se puede realizar esta operación. Vuelva a intentarlo más tarde.",
10
10
  "error.cross_origin": "No se puede realizar esta operación. Vuelva a intentarlo más tarde.",
11
+ "error.origin_required": "Se rechazó la solicitud: el navegador no indicó que procediera de la página de esta App. Vuelva a abrir la App en un navegador actual.",
11
12
  "error.internal": "No se puede realizar esta operación. Vuelva a intentarlo más tarde.",
12
13
  "error.json_required": "No se puede realizar esta operación. Vuelva a intentarlo más tarde.",
13
14
  "error.method_not_allowed": "No se puede realizar esta operación. Vuelva a intentarlo más tarde.",
14
15
  "error.network": "Error de la conexión. Compruebe su conexión de red.",
15
16
  "error.request_too_large": "No se puede realizar esta operación. Vuelva a intentarlo más tarde.",
17
+ "error.request_timeout": "No se puede realizar esta operación. Vuelva a intentarlo más tarde.",
18
+ "error.request_canceled": "No se puede realizar esta operación. Vuelva a intentarlo más tarde.",
16
19
  "error.session_required": "Conexión caducada. Vuelva a conectarse.",
17
20
  "error.unknown_method": "No se puede realizar esta operación. Vuelva a intentarlo más tarde.",
18
21
  "greeting.adminOnly": "Solo los miembros del grupo administrators pueden cambiar el saludo.",
@@ -8,11 +8,14 @@
8
8
  "error.bad_request": "Vérifiez la valeur saisie et réessayez.",
9
9
  "error.bad_response": "Impossible d'effectuer cette opération. Veuillez réessayer ultérieurement.",
10
10
  "error.cross_origin": "Impossible d'effectuer cette opération. Veuillez réessayer ultérieurement.",
11
+ "error.origin_required": "La requête a été refusée : le navigateur n’a pas indiqué qu’elle provenait de la page de cette App. Rouvrez l’App dans un navigateur récent.",
11
12
  "error.internal": "Impossible d'effectuer cette opération. Veuillez réessayer ultérieurement.",
12
13
  "error.json_required": "Impossible d'effectuer cette opération. Veuillez réessayer ultérieurement.",
13
14
  "error.method_not_allowed": "Impossible d'effectuer cette opération. Veuillez réessayer ultérieurement.",
14
15
  "error.network": "La connexion a échoué. Vérifiez vos paramètres réseau.",
15
16
  "error.request_too_large": "Impossible d'effectuer cette opération. Veuillez réessayer ultérieurement.",
17
+ "error.request_timeout": "Impossible d'effectuer cette opération. Veuillez réessayer ultérieurement.",
18
+ "error.request_canceled": "Impossible d'effectuer cette opération. Veuillez réessayer ultérieurement.",
16
19
  "error.session_required": "La connexion a expiré. Reconnectez-vous.",
17
20
  "error.unknown_method": "Impossible d'effectuer cette opération. Veuillez réessayer ultérieurement.",
18
21
  "greeting.adminOnly": "Seuls les membres du groupe administrators peuvent modifier le message d'accueil.",
@@ -8,11 +8,14 @@
8
8
  "error.bad_request": "Controllare il valore inserito e riprovare.",
9
9
  "error.bad_response": "Impossibile eseguire questa operazione. Riprovare più tardi.",
10
10
  "error.cross_origin": "Impossibile eseguire questa operazione. Riprovare più tardi.",
11
+ "error.origin_required": "La richiesta è stata rifiutata: il browser non ha indicato che proviene dalla pagina di questa App. Riaprire l’App in un browser aggiornato.",
11
12
  "error.internal": "Impossibile eseguire questa operazione. Riprovare più tardi.",
12
13
  "error.json_required": "Impossibile eseguire questa operazione. Riprovare più tardi.",
13
14
  "error.method_not_allowed": "Impossibile eseguire questa operazione. Riprovare più tardi.",
14
15
  "error.network": "Connessione non riuscita. Controllare le impostazioni di rete.",
15
16
  "error.request_too_large": "Impossibile eseguire questa operazione. Riprovare più tardi.",
17
+ "error.request_timeout": "Impossibile eseguire questa operazione. Riprovare più tardi.",
18
+ "error.request_canceled": "Impossibile eseguire questa operazione. Riprovare più tardi.",
16
19
  "error.session_required": "Connessione scaduta. Accedere nuovamente.",
17
20
  "error.unknown_method": "Impossibile eseguire questa operazione. Riprovare più tardi.",
18
21
  "greeting.adminOnly": "Solo i membri del gruppo administrators possono modificare il saluto.",
@@ -8,11 +8,14 @@
8
8
  "error.bad_request": "入力した値を確認して、もう一度お試しください。",
9
9
  "error.bad_response": "この操作は実行できません。後でもう一度お試しください。",
10
10
  "error.cross_origin": "この操作は実行できません。後でもう一度お試しください。",
11
+ "error.origin_required": "リクエストは拒否されました。ブラウザーがこの App のページからのリクエストであることを示しませんでした。最新のブラウザーで App を開き直してください。",
11
12
  "error.internal": "この操作は実行できません。後でもう一度お試しください。",
12
13
  "error.json_required": "この操作は実行できません。後でもう一度お試しください。",
13
14
  "error.method_not_allowed": "この操作は実行できません。後でもう一度お試しください。",
14
15
  "error.network": "接続に失敗しました。ネットワーク設定を確認してください。",
15
16
  "error.request_too_large": "この操作は実行できません。後でもう一度お試しください。",
17
+ "error.request_timeout": "この操作は実行できません。後でもう一度お試しください。",
18
+ "error.request_canceled": "この操作は実行できません。後でもう一度お試しください。",
16
19
  "error.session_required": "接続の期限が切れています。もう 1 度サイン インしてください。",
17
20
  "error.unknown_method": "この操作は実行できません。後でもう一度お試しください。",
18
21
  "greeting.adminOnly": "あいさつを変更できるのは administrators グループのメンバーのみです。",
@@ -8,11 +8,14 @@
8
8
  "error.bad_request": "입력한 값을 확인한 후 다시 시도하십시오.",
9
9
  "error.bad_response": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
10
10
  "error.cross_origin": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
11
+ "error.origin_required": "요청이 거부되었습니다. 브라우저가 이 App 페이지에서 보낸 요청임을 알리지 않았습니다. 최신 브라우저에서 App을 다시 여십시오.",
11
12
  "error.internal": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
12
13
  "error.json_required": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
13
14
  "error.method_not_allowed": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
14
15
  "error.network": "연결 실패. 네트워크 설정을 확인해 주십시오.",
15
16
  "error.request_too_large": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
17
+ "error.request_timeout": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
18
+ "error.request_canceled": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
16
19
  "error.session_required": "연결 만료됨. 다시 로그인 해 주십시오.",
17
20
  "error.unknown_method": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
18
21
  "greeting.adminOnly": "administrators 그룹의 구성원만 인사말을 변경할 수 있습니다.",
@@ -8,11 +8,14 @@
8
8
  "error.bad_request": "请检查输入的值后重试。",
9
9
  "error.bad_response": "无法执行此操作。请稍后再试。",
10
10
  "error.cross_origin": "无法执行此操作。请稍后再试。",
11
+ "error.origin_required": "请求已被拒绝:浏览器没有表明它来自此 App 的页面。请使用新版浏览器重新打开 App。",
11
12
  "error.internal": "无法执行此操作。请稍后再试。",
12
13
  "error.json_required": "无法执行此操作。请稍后再试。",
13
14
  "error.method_not_allowed": "无法执行此操作。请稍后再试。",
14
15
  "error.network": "无法联机,请检查你的网络设置。",
15
16
  "error.request_too_large": "无法执行此操作。请稍后再试。",
17
+ "error.request_timeout": "无法执行此操作。请稍后再试。",
18
+ "error.request_canceled": "无法执行此操作。请稍后再试。",
16
19
  "error.session_required": "联机超时,请重新登录。",
17
20
  "error.unknown_method": "无法执行此操作。请稍后再试。",
18
21
  "greeting.adminOnly": "只有 administrators 群组的成员可以更改问候语。",
@@ -8,11 +8,14 @@
8
8
  "error.bad_request": "請檢查輸入的值後再試一次。",
9
9
  "error.bad_response": "無法執行此操作。請稍後再試。",
10
10
  "error.cross_origin": "無法執行此操作。請稍後再試。",
11
+ "error.origin_required": "要求已被拒絕:瀏覽器沒有表明它來自這個 App 的頁面。請使用新版瀏覽器重新開啟 App。",
11
12
  "error.internal": "無法執行此操作。請稍後再試。",
12
13
  "error.json_required": "無法執行此操作。請稍後再試。",
13
14
  "error.method_not_allowed": "無法執行此操作。請稍後再試。",
14
15
  "error.network": "無法連線,請檢查你的網路設定。",
15
16
  "error.request_too_large": "無法執行此操作。請稍後再試。",
17
+ "error.request_timeout": "無法執行此操作。請稍後再試。",
18
+ "error.request_canceled": "無法執行此操作。請稍後再試。",
16
19
  "error.session_required": "連線逾時。請重新登入。",
17
20
  "error.unknown_method": "無法執行此操作。請稍後再試。",
18
21
  "greeting.adminOnly": "只有 administrators 群組的成員可以變更問候語。",
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import {computed,onBeforeUnmount,onMounted,ref} from 'vue'
3
- import {Button,Callout} from '@synopackageland/ui'
3
+ import {Button,Callout,SectionSkeleton} from '@synopackageland/ui'
4
4
  import type {Context} from '@synopackageland/sdk'
5
5
  import {BackgroundSavePanel, DelegatePanel, ExecutorSettings, createBackgroundPickerProvider, createBackgroundSaveFacade, saveCopy} from '@synopackageland/sdk/vue'
6
6
  const props=defineProps<{ctx:Context;admin:boolean}>()
@@ -20,13 +20,15 @@ async function detect(){
20
20
  }else mode.value='legacy'
21
21
  }catch{if(!disposed)mode.value='unavailable'}
22
22
  }
23
+ const executor=ref<{refresh():unknown}>(),panel=ref<{refresh():unknown}>()
24
+ defineExpose({refresh(){void executor.value?.refresh();void panel.value?.refresh()}})
23
25
  onMounted(()=>{if(!props.ctx.delegate)void detect()})
24
26
  onBeforeUnmount(()=>{disposed=true;provider.dispose()})
25
27
  </script>
26
28
  <template>
27
- <ExecutorSettings :admin="admin" :locale="ctx.locale" :ctx="ctx" @confirmed="revision++;detect()" />
28
- <BackgroundSavePanel v-if="mode==='long'" :key="revision" :sdk="sdk" :admin="admin" :locale="ctx.locale" :picker="{provider,keys}" />
29
- <DelegatePanel v-else-if="mode==='legacy'" :ctx="ctx" />
30
- <p v-else-if="mode==='loading'" role="status">{{ copy.loading }}</p>
31
- <Callout v-else tone="danger" role="alert">{{ copy.error }} <Button variant="secondary" @click="detect">{{ copy.refresh }}</Button></Callout>
29
+ <ExecutorSettings ref="executor" :admin="admin" :locale="ctx.locale" :ctx="ctx" @confirmed="revision++;detect()" />
30
+ <BackgroundSavePanel v-if="mode==='long'" ref="panel" :key="revision" :sdk="sdk" :admin="admin" :locale="ctx.locale" :picker="{provider,keys}" sample-save />
31
+ <DelegatePanel v-else-if="mode==='legacy'" ref="panel" :ctx="ctx" />
32
+ <SectionSkeleton v-else-if="mode==='loading'" />
33
+ <Callout v-else tone="danger" role="alert">{{ copy.error }} <Button variant="secondary" @click="detect">{{ copy.retry }}</Button></Callout>
32
34
  </template>
@@ -33,21 +33,25 @@ test('the capability surface uses an explicit long-running facade without legacy
33
33
  test('the default surface binds the real routes and the session-only picker',async()=>{
34
34
  const grant={id:'g1',key:'save',path:'/home',username:'normal1',lifetime:'until-revoked'}
35
35
  let selected=false
36
- const call=vi.fn(async(method:string)=>{
36
+ const call=vi.fn(async(method:string,params?:unknown)=>{
37
37
  if(method==='delegate.status')return {state:'ready',disabled:false,lifetime:'until-revoked',destinations:['save']}
38
38
  if(method==='delegate.destination')return {key:'save',selection:{scope:'home',root:'/home',generation:0},destination:selected?grant:null,status:selected?'active':'missing'}
39
39
  if(method==='delegate.list')return {delegations:selected?[{delegation_id:'g1',destination:'save',path:'/home',subject:{username:'normal1'},lifetime:'until-revoked',revoked:false}]:[]}
40
40
  if(method==='delegate.operations.list')return {operations:[]}
41
41
  if(method==='bg.browse')return {shares:[{name:'home',path:'/home',isdir:true}],offset:0,total:1}
42
42
  if(method==='delegate.select_destination'){selected=true;return grant}
43
+ if(method==='delegate.example.export')return {delegation_id:'g1',job_id:(params as {job_id:string}).job_id}
43
44
  throw {code:'unexpected'}
44
45
  })
45
46
  const root=render({call,locale:'en'});await settle()
46
- button(root,'Choose save destination (save)').click();await settle()
47
+ button(root,'Choose save destination').click();await settle()
47
48
  // The SDK picker selects a folder with a row click.
48
49
  const row=root.querySelector<HTMLElement>('tr[data-row-key="/home"]')!;expect(row).toBeTruthy();row.dispatchEvent(new MouseEvent('click',{bubbles:true}));await settle()
49
50
  button(root,'Confirm selection').click();await settle()
50
51
  expect(call).toHaveBeenCalledWith('delegate.select_destination',{key:'save',path:'/home'})
52
+ // The template demonstrates the save flow; SDK 0.9.9 shows it only on request.
53
+ button(root,'Save example file').click();await settle()
54
+ expect(call).toHaveBeenCalledWith('delegate.example.export',expect.objectContaining({key:'save'}))
51
55
  })
52
56
  test('the app mount forwards ctx.executor to administrator setup only',async()=>{
53
57
  for(const admin of [true,false]){
@@ -76,3 +80,25 @@ test('the app mount forwards ctx.executor to administrator setup only',async()=>
76
80
  }
77
81
  }
78
82
  })
83
+ test('the header Refresh reloads the save destinations',async()=>{
84
+ const call=vi.fn(async(method:string)=>{
85
+ if(method==='app.status')return {package:'DelegateApp',version:'1',user:'alice',admin:false}
86
+ if(method==='delegate.status')return {state:'ready',disabled:false,lifetime:'until-revoked',destinations:['save']}
87
+ if(method==='delegate.destination')return {key:'save',selection:{scope:'home',root:'/home',generation:0},destination:null,status:'missing'}
88
+ if(method==='delegate.list')return {delegations:[]}
89
+ if(method==='delegate.operations.list')return {operations:[]}
90
+ throw {code:'unknown_method'}
91
+ })
92
+ const root=document.createElement('div');document.body.append(root)
93
+ const app=mount(root,{locale:'en',call,reopen:()=>()=>{}} as any)
94
+ cleanup.push(()=>{app.unmount();root.remove()});await settle()
95
+ const reads=()=>call.mock.calls.filter(([method])=>method==='delegate.destination').length
96
+ const before=reads();expect(before).toBeGreaterThan(0)
97
+ button(root,'Refresh').click();await settle()
98
+ expect(reads()).toBeGreaterThan(before)
99
+ })
100
+ test('the panels show a skeleton while the delegate status loads',async()=>{
101
+ const call=vi.fn(async(method:string)=>{if(method==='delegate.status')return new Promise(()=>{});throw {code:'unexpected'}})
102
+ const root=render({call,locale:'en'});await settle()
103
+ expect(root.querySelector('[data-ui-skeleton-line]')).not.toBeNull()
104
+ })
@@ -83,6 +83,7 @@ func TestBackgroundRequestSubmitBusyAndProductionConstruction(t *testing.T) {
83
83
  }
84
84
  r := httptest.NewRequest("POST", "/api.cgi", strings.NewReader(`{"method":"bg.jobs.list","params":{}}`))
85
85
  r.Header.Set("Content-Type", "application/json")
86
+ r.Header.Set("Sec-Fetch-Site", "same-origin")
86
87
  w := httptest.NewRecorder()
87
88
  srv.ServeHTTP(w, r)
88
89
  if !strings.Contains(w.Body.String(), `"jobs_busy"`) {
@@ -39,3 +39,21 @@ test('the background panel pauses a running job and resumes a paused one',async(
39
39
  expect(call).toHaveBeenCalledWith('bg.jobs.resume',{id:'held'})
40
40
  }finally{app.unmount();root.remove()}
41
41
  })
42
+ test('the header Refresh reloads the background panel',async()=>{
43
+ const call=vi.fn(async(method:string):Promise<unknown>=>{
44
+ if(method==='app.status')return {package:'Jobs',version:'1',user:'alice',admin:false}
45
+ if(method==='bg.status')return {owner:{user:'alice',uid:'1024'},admin:false,revision:1,worker:{username:'pkg',alive:true},limits:{maxInputBytes:1024,maxQueued:8,maxOwnerPending:4,maxOwnerBytes:4096,jobTimeoutSeconds:60}}
46
+ if(method==='bg.jobs.list')return {jobs:[]}
47
+ if(method==='bg.schedules.list')return {schedules:[]}
48
+ return {}
49
+ })
50
+ const root=document.createElement('div');document.body.append(root)
51
+ const app=mount(root,{locale:'en',call,reopen:()=>()=>{}} as unknown as Context)
52
+ const reads=()=>call.mock.calls.filter(([method])=>method==='bg.jobs.list').length
53
+ try{
54
+ await settle();const before=reads()
55
+ // The header button comes first; the panels below may have their own.
56
+ ;(Array.from(root.querySelectorAll('button')).find(b=>b.textContent==='Refresh') as HTMLButtonElement).click();await settle()
57
+ expect(reads()).toBeGreaterThan(before)
58
+ }finally{app.unmount();root.remove()}
59
+ })
@@ -7,8 +7,8 @@
7
7
  "name": "native-dsm-vue-app",
8
8
  "dependencies": {
9
9
  "@lucide/vue": "1.48.0",
10
- "@synopackageland/sdk": "0.9.8",
11
- "@synopackageland/ui": "0.9.2",
10
+ "@synopackageland/sdk": "0.9.9",
11
+ "@synopackageland/ui": "0.9.3",
12
12
  "vue": "3.5.43"
13
13
  },
14
14
  "devDependencies": {
@@ -1148,13 +1148,13 @@
1148
1148
  }
1149
1149
  },
1150
1150
  "node_modules/@synopackageland/sdk": {
1151
- "version": "0.9.8",
1152
- "resolved": "https://registry.npmjs.org/@synopackageland/sdk/-/sdk-0.9.8.tgz",
1153
- "integrity": "sha512-MYqEpy8/3A4z3hvffXHvNJxYw8HcIGtCKoUUx+hsG5buK+jUnFCPKrzsGuSs6mggQGZbBD/E7HdNJ00xKFQpyA==",
1151
+ "version": "0.9.9",
1152
+ "resolved": "https://registry.npmjs.org/@synopackageland/sdk/-/sdk-0.9.9.tgz",
1153
+ "integrity": "sha512-O73D+s+yJPmYLfZ2l261S1izqa5lAc9hgbuBScE4uoKyydUDfWEhAmb7CUM6Er25wY8nYqG480GMcyfLuESFGw==",
1154
1154
  "license": "MIT",
1155
1155
  "peerDependencies": {
1156
1156
  "@lucide/vue": "^1.48.0",
1157
- "@synopackageland/ui": "^0.9.1",
1157
+ "@synopackageland/ui": "^0.9.3",
1158
1158
  "vue": "^3.5.0"
1159
1159
  },
1160
1160
  "peerDependenciesMeta": {
@@ -1167,9 +1167,9 @@
1167
1167
  }
1168
1168
  },
1169
1169
  "node_modules/@synopackageland/ui": {
1170
- "version": "0.9.2",
1171
- "resolved": "https://registry.npmjs.org/@synopackageland/ui/-/ui-0.9.2.tgz",
1172
- "integrity": "sha512-GXYs6oVed0tIb4J5D5aDYoLJMs7haq0DBWKrPP1cLU6QKUQ6EKaIwqWHv0sVdu530FCARjcB1PrWwWVS9WOsvA==",
1170
+ "version": "0.9.3",
1171
+ "resolved": "https://registry.npmjs.org/@synopackageland/ui/-/ui-0.9.3.tgz",
1172
+ "integrity": "sha512-CcCpSiQ2y4GJnbWjBxRrPO6QzLboMdEzqgbMdpXGaEI3IaTrxwS6zy8yXuuMBQHuqAcfBf3jL7fmcL9A58oVqw==",
1173
1173
  "license": "MIT",
1174
1174
  "dependencies": {
1175
1175
  "@internationalized/date": "3.12.4",
@@ -2538,9 +2538,9 @@
2538
2538
  "license": "MIT"
2539
2539
  },
2540
2540
  "node_modules/zod": {
2541
- "version": "4.6.6",
2542
- "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.6.tgz",
2543
- "integrity": "sha512-4UmzQUF5fxvfcYHlbjBmejU+w+hbDnWFx4aS8kBx7nPBHcO2SqPbpAG8snbdg+Xse4pRefR98hgjFgIMCI/UhA==",
2541
+ "version": "4.6.7",
2542
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz",
2543
+ "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==",
2544
2544
  "license": "MIT",
2545
2545
  "funding": {
2546
2546
  "url": "https://github.com/sponsors/colinhacks"
@@ -8,8 +8,8 @@
8
8
  },
9
9
  "dependencies": {
10
10
  "@lucide/vue": "1.48.0",
11
- "@synopackageland/sdk": "0.9.8",
12
- "@synopackageland/ui": "0.9.2",
11
+ "@synopackageland/sdk": "0.9.9",
12
+ "@synopackageland/ui": "0.9.3",
13
13
  "vue": "3.5.43"
14
14
  },
15
15
  "devDependencies": {
@@ -45,6 +45,14 @@ async function refresh() {
45
45
  } catch(e) {if(!disposed && !(early && login.enabled.value)) error.value = message(e)}
46
46
  finally {if(!disposed) {busy.value=false; if(again) {again=false; void refresh()}}}
47
47
  }
48
+ // The header Refresh also reloads the panels below and reads the portal again.
49
+ const panels = ref<{refresh(): unknown}>()
50
+ const background = ref<{refresh(): unknown}>()
51
+ const portalReads = ref(0)
52
+ const portalKey = computed(() => `${app.value?.user}:${portalReads.value}`)
53
+ function refreshAll() {
54
+ void refresh(); void panels.value?.refresh(); void background.value?.refresh(); portalReads.value++
55
+ }
48
56
  async function save() {
49
57
  if(disposed || busy.value) return
50
58
  busy.value = true; error.value='';notice.value=''
@@ -72,27 +80,26 @@ onUnmounted(()=>{disposed=true;unsubscribe?.();login.dispose();text.value=''})
72
80
  <ConfigProvider :locale="uiLocale(ctx.locale)">
73
81
  <LoginPage v-if="login.ready.value && login.enabled.value && !login.subject.value" :session="login" :locale="ctx.locale" />
74
82
  <AppShell v-else v-model="page" :locale="ctx.locale" :pages="pages" :description="subtitle">
75
- <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>
83
+ <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>
76
84
  <div class="integration-app" :aria-busy="busy" :aria-label="copy.settings">
77
- <p v-if="busy" role="status">{{ copy.loading }}</p>
78
85
  <Callout v-if="error" tone="danger" :title="copy.actionRequired"><span role="alert">{{ error }}</span></Callout>
79
86
  <p v-if="notice" role="status">{{ notice }}</p>
80
87
  <div data-page="status" v-show="page==='status'">
81
- <SettingsSection :title="copy.statusTitle" :collapsible="false">
88
+ <SettingsSection :title="copy.statusTitle" :collapsible="false" :loading="busy && !app">
82
89
  <p v-if="app">{{ app.package }} · {{ app.user }} · {{ app.version }}</p>
83
90
  <p v-else-if="error" role="status">{{ copy.statusError }}</p>
84
91
  </SettingsSection>
85
- <PortalSettings v-if="ctx.portal && app" :key="app.user" :portal="ctx.portal" />
92
+ <PortalSettings v-if="ctx.portal && app" :key="portalKey" :portal="ctx.portal" />
86
93
  <SettingsSection v-if="app?.admin" :title="copy.greeting" :collapsible="false">
87
- <Form @submit.prevent="save"><FormItem :label="copy.greeting"><Textarea v-model="text" :disabled="busy" /></FormItem>
94
+ <Form direction="vertical" @submit.prevent="save"><FormItem :label="copy.greeting"><Textarea v-model="text" :disabled="busy" /></FormItem>
88
95
  <FormActions><Button variant="primary" :disabled="busy || !text.trim()" @click="save">{{ copy.save }}</Button></FormActions>
89
96
  </Form>
90
97
  </SettingsSection>
91
98
  </div>
92
- <CapabilityPanels v-if="app" :key="app.user" :ctx="ctx" :admin="app.admin" />
99
+ <CapabilityPanels v-if="app" ref="panels" :key="app.user" :ctx="ctx" :admin="app.admin" />
93
100
  <FilesPage v-if="page==='status'" :ctx="ctx" />
94
101
  <div data-page="background" v-show="page==='background'">
95
- <BackgroundPanel v-if="backgroundEnabled && app" :key="app.user" :ctx="ctx" />
102
+ <BackgroundPanel v-if="backgroundEnabled && app" ref="background" :key="app.user" :ctx="ctx" />
96
103
  </div>
97
104
  </div>
98
105
  </AppShell>
@@ -1,6 +1,9 @@
1
1
  <script setup lang="ts">
2
+ import {ref} from 'vue'
2
3
  import type {Context} from '@synopackageland/sdk'
3
4
  import {ExecutorSettings} from '@synopackageland/sdk/vue'
4
5
  defineProps<{ctx:Context;admin:boolean}>()
6
+ const executor=ref<{refresh():unknown}>()
7
+ defineExpose({refresh:()=>void executor.value?.refresh()})
5
8
  </script>
6
- <template><ExecutorSettings v-if="ctx.executor" :admin="admin" :locale="ctx.locale" /></template>
9
+ <template><ExecutorSettings v-if="ctx.executor" ref="executor" :admin="admin" :locale="ctx.locale" /></template>
@@ -2,7 +2,6 @@
2
2
  "refresh": "Aktualisieren",
3
3
  "signOut": "Abmelden",
4
4
  "settings": "Paketeinstellungen",
5
- "loading": "Paketdaten werden geladen…",
6
5
  "actionRequired": "Aktion erforderlich",
7
6
  "statusTitle": "Paketstatus",
8
7
  "statusError": "Der Status konnte nicht geladen werden. Aktualisieren Sie die Ansicht und versuchen Sie es erneut.",
@@ -2,7 +2,6 @@
2
2
  "refresh": "Refresh",
3
3
  "signOut": "Sign out",
4
4
  "settings": "Package settings",
5
- "loading": "Loading package data…",
6
5
  "actionRequired": "Action required",
7
6
  "statusTitle": "Package status",
8
7
  "statusError": "Status could not be loaded. Refresh to try again.",
@@ -2,7 +2,6 @@
2
2
  "refresh": "Actualizar",
3
3
  "signOut": "Cerrar sesión",
4
4
  "settings": "Configuración del paquete",
5
- "loading": "Cargando datos del paquete…",
6
5
  "actionRequired": "Acción necesaria",
7
6
  "statusTitle": "Estado del paquete",
8
7
  "statusError": "No se pudo cargar el estado. Actualice e inténtelo de nuevo.",
@@ -2,7 +2,6 @@
2
2
  "refresh": "Actualiser",
3
3
  "signOut": "Se déconnecter",
4
4
  "settings": "Paramètres du paquet",
5
- "loading": "Chargement des données du paquet…",
6
5
  "actionRequired": "Action requise",
7
6
  "statusTitle": "État du paquet",
8
7
  "statusError": "Impossible de charger l’état. Actualisez et réessayez.",
@@ -2,7 +2,6 @@
2
2
  "refresh": "Aggiorna",
3
3
  "signOut": "Disconnetti",
4
4
  "settings": "Impostazioni del pacchetto",
5
- "loading": "Caricamento dei dati del pacchetto…",
6
5
  "actionRequired": "Azione richiesta",
7
6
  "statusTitle": "Stato del pacchetto",
8
7
  "statusError": "Impossibile caricare lo stato. Aggiornare e riprovare.",
@@ -2,7 +2,6 @@
2
2
  "refresh": "最新の情報に更新",
3
3
  "signOut": "サインアウト",
4
4
  "settings": "パッケージ設定",
5
- "loading": "パッケージデータを読み込み中…",
6
5
  "actionRequired": "対応が必要です",
7
6
  "statusTitle": "パッケージの状態",
8
7
  "statusError": "状態を読み込めませんでした。最新の情報に更新してから、もう一度お試しください。",
@@ -2,7 +2,6 @@
2
2
  "refresh": "새로 고침",
3
3
  "signOut": "로그아웃",
4
4
  "settings": "패키지 설정",
5
- "loading": "패키지 데이터를 불러오는 중…",
6
5
  "actionRequired": "조치 필요",
7
6
  "statusTitle": "패키지 상태",
8
7
  "statusError": "상태를 불러올 수 없습니다. 새로 고친 후 다시 시도하십시오.",
@@ -2,7 +2,6 @@
2
2
  "refresh": "刷新",
3
3
  "signOut": "注销",
4
4
  "settings": "套件设置",
5
- "loading": "正在加载套件数据…",
6
5
  "actionRequired": "需要处理",
7
6
  "statusTitle": "套件状态",
8
7
  "statusError": "无法加载状态,请刷新后重试。",
@@ -2,7 +2,6 @@
2
2
  "refresh": "重新整理",
3
3
  "signOut": "登出",
4
4
  "settings": "套件設定",
5
- "loading": "正在載入套件資料…",
6
5
  "actionRequired": "需要處理",
7
6
  "statusTitle": "套件狀態",
8
7
  "statusError": "無法載入狀態,請重新整理後再試。",
@@ -55,6 +55,22 @@ test('a portal app shows the Application Portal section on the status page',asyn
55
55
  app.unmount()
56
56
  }finally{without.close();f.root.remove()}
57
57
  })
58
+ test('the header Refresh reads the portal and executor status again',async()=>{
59
+ const f=fixture('Portal')
60
+ try{
61
+ await settle();f.app.unmount()
62
+ const status={state:'active',alias:'portal',url:'https://nas.example/portal/',canManage:false,controlPanel:false,defaultAlias:'portal'} as const
63
+ const portal=vi.fn(async()=>status),executor=vi.fn(async()=>({state:'ready' as const}))
64
+ f.ctx.portal={messages:{title:'Application Portal',link:'Direct link:'},status:portal,apply:vi.fn(),disable:vi.fn(),openControlPanel:vi.fn(),mountPanel:vi.fn()}
65
+ f.ctx.executor={status:executor,authorize:vi.fn(),control:vi.fn()} as unknown as Context['executor']
66
+ const app=mount(f.root,f.ctx);await settle()
67
+ const [portalReads,executorReads]=[portal.mock.calls.length,executor.mock.calls.length]
68
+ ;[...f.root.querySelectorAll('button')].find(n=>n.textContent==='Refresh')!.click();await settle()
69
+ expect(portal.mock.calls.length).toBeGreaterThan(portalReads)
70
+ expect(executor.mock.calls.length).toBeGreaterThan(executorReads)
71
+ app.unmount()
72
+ }finally{f.root.remove()}
73
+ })
58
74
  test('the app renders and loads status while the session check is still pending',async()=>{
59
75
  const call=vi.fn(async(method:string)=>{
60
76
  if(method==='identity.status')return new Promise(()=>{})
@@ -129,3 +145,24 @@ test('outside a DSM window dialogs stay inside the app',async()=>{
129
145
  try{await settle();expect(document.querySelector('[data-ui-floating]')).toBeNull()}finally{f.close()}
130
146
  }
131
147
  })
148
+ test('the status page follows the UI layout rules',async()=>{
149
+ let answer:(value:unknown)=>void=()=>{}
150
+ const call=vi.fn(async(method:string)=>{
151
+ if(method==='app.status')return new Promise(resolve=>{answer=resolve})
152
+ if(method==='greeting.get')return {text:'Hello'}
153
+ throw {code:'unknown_method'}
154
+ })
155
+ const root=document.createElement('div');document.body.append(root)
156
+ const app=mount(root,{locale:'en',call,reopen:()=>()=>{}} as unknown as Context)
157
+ try{
158
+ await settle()
159
+ // A loading section keeps its title and shows a skeleton, not a sentence.
160
+ expect(root.textContent).toContain('Package status')
161
+ expect(root.querySelector('[data-ui-skeleton-line]')).not.toBeNull()
162
+ expect(root.textContent).not.toContain('Loading package data')
163
+ answer({package:'Pkg',version:'1',user:'admin',admin:true});await settle()
164
+ expect(root.querySelector('[data-ui-skeleton-line]')).toBeNull()
165
+ // Labels sit above their fields.
166
+ expect(root.querySelector('textarea')!.closest('[data-form-item]')!.getAttribute('data-direction')).toBe('vertical')
167
+ }finally{app.unmount();root.remove()}
168
+ })
@@ -10,12 +10,12 @@ const uses=(file:string,tag:string)=>new RegExp(`<${tag}[\\s>/]`).test(template(
10
10
  test.each(['select','option','textarea','label','input','a'])('no vue template renders a native <%s>',tag=>{
11
11
  expect(sources(root).filter(file=>uses(file,tag))).toEqual([])
12
12
  })
13
- // Form direction and the control id come from the UI: `direction="auto"` stacks labels in a
14
- // narrow package window, and a control inside a FormItem binds its id/ARIA by itself.
15
- test('FormItem controls rely on the UI auto layout and id binding',()=>{
13
+ // UI layout rules: labels sit above their controls, so a page Form is vertical; a control
14
+ // inside a FormItem binds its id/ARIA by itself.
15
+ test('FormItem labels sit above their controls and the UI binds the control id',()=>{
16
16
  const items=sources(root).filter(file=>uses(file,'FormItem'))
17
17
  expect(items.length).toBeGreaterThan(0)
18
- expect(items.filter(file=>/direction="vertical"|field\.controlId|#default="\{field\}"/.test(template(file)))).toEqual([])
18
+ expect(items.filter(file=>/<Form(?![^>]*direction="vertical")[\s>]|field\.controlId|#default="\{field\}"/.test(template(file)))).toEqual([])
19
19
  })
20
20
  // UI built-in text follows the Provider locale (nine locales), so every app root sets it once and no
21
21
  // component repeats a per-control locale or download label.
@@ -104,8 +104,8 @@ dependencies = [
104
104
 
105
105
  [[package]]
106
106
  name = "syno-pkg-land-sdk"
107
- version = "0.9.8"
108
- source = "git+https://github.com/synopackageland/syno-pkg-land-sdk?tag=v0.9.8#af09d3ee156e631edc81025561dcbb6e120fef32"
107
+ version = "0.9.9"
108
+ source = "git+https://github.com/synopackageland/syno-pkg-land-sdk?tag=v0.9.9#28da1726b0c06f4428153555314d33c7a3ca8a8b"
109
109
  dependencies = [
110
110
  "libc",
111
111
  "serde",
@@ -17,7 +17,7 @@ libc = "0.2"
17
17
  serde = { version = "1", features = ["derive"] }
18
18
  serde_json = "1"
19
19
  # The SDK version is the tag; `synopkgland check` reads it from Cargo.lock.
20
- syno-pkg-land-sdk = { git = "https://github.com/synopackageland/syno-pkg-land-sdk", tag = "v0.9.8" }
20
+ syno-pkg-land-sdk = { git = "https://github.com/synopackageland/syno-pkg-land-sdk", tag = "v0.9.9" }
21
21
 
22
22
  [profile.release]
23
23
  # A handler panic becomes an "internal" reply (api::Server); abort would end the CGI instead.
@@ -1,6 +1,6 @@
1
1
  Native Rust package build dependencies (crates linked into the backend binaries)
2
2
 
3
- syno-pkg-land-sdk v0.9.8 (MIT)
3
+ syno-pkg-land-sdk v0.9.9 (MIT)
4
4
 
5
5
  MIT License
6
6
 
@@ -130,6 +130,8 @@ pub fn api_request(method: &str, params: &str) -> Request {
130
130
  let body = format!(r#"{{"method":"{method}","params":{params}}}"#);
131
131
  let mut req = Request::new("POST", "/api.cgi").with_body(body);
132
132
  req.header.set("Content-Type", "application/json");
133
+ // A browser says which page sent a POST; the SDK refuses one that does not.
134
+ req.header.set("Sec-Fetch-Site", "same-origin");
133
135
  req
134
136
  }
135
137
 
@@ -624,7 +624,9 @@
624
624
  "error.method_not_allowed",
625
625
  "error.json_required",
626
626
  "error.cross_origin",
627
- "error.request_too_large"
627
+ "error.request_too_large",
628
+ "error.request_timeout",
629
+ "error.request_canceled"
628
630
  ]
629
631
  },
630
632
  {
@@ -8,11 +8,14 @@
8
8
  "error.bad_request": "Überprüfen Sie den eingegebenen Wert und versuchen Sie es erneut.",
9
9
  "error.bad_response": "Dieser Vorgang kann nicht ausgeführt werden. Bitte versuchen Sie es später erneut.",
10
10
  "error.cross_origin": "Dieser Vorgang kann nicht ausgeführt werden. Bitte versuchen Sie es später erneut.",
11
+ "error.origin_required": "Die Anfrage wurde abgelehnt: Der Browser hat nicht angegeben, dass sie von der Seite dieser App stammt. Öffnen Sie die App in einem aktuellen Browser erneut.",
11
12
  "error.internal": "Dieser Vorgang kann nicht ausgeführt werden. Bitte versuchen Sie es später erneut.",
12
13
  "error.json_required": "Dieser Vorgang kann nicht ausgeführt werden. Bitte versuchen Sie es später erneut.",
13
14
  "error.method_not_allowed": "Dieser Vorgang kann nicht ausgeführt werden. Bitte versuchen Sie es später erneut.",
14
15
  "error.network": "Verbindung fehlgeschlagen. Bitte überprüfen Sie Ihre Netzwerkeinstellungen.",
15
16
  "error.request_too_large": "Dieser Vorgang kann nicht ausgeführt werden. Bitte versuchen Sie es später erneut.",
17
+ "error.request_timeout": "Dieser Vorgang kann nicht ausgeführt werden. Bitte versuchen Sie es später erneut.",
18
+ "error.request_canceled": "Dieser Vorgang kann nicht ausgeführt werden. Bitte versuchen Sie es später erneut.",
16
19
  "error.session_required": "Verbindung abgelaufen. Melden Sie sich erneut an.",
17
20
  "error.unknown_method": "Dieser Vorgang kann nicht ausgeführt werden. Bitte versuchen Sie es später erneut.",
18
21
  "greeting.adminOnly": "Nur Mitglieder der Gruppe administrators können den Gruß ändern.",
@@ -8,11 +8,14 @@
8
8
  "error.bad_request": "Check the entered value and try again.",
9
9
  "error.bad_response": "Unable to perform this operation. Please try again later.",
10
10
  "error.cross_origin": "Unable to perform this operation. Please try again later.",
11
+ "error.origin_required": "The request was refused: the browser did not show that it came from this App's page. Open the App again in a current browser.",
11
12
  "error.internal": "Unable to perform this operation. Please try again later.",
12
13
  "error.json_required": "Unable to perform this operation. Please try again later.",
13
14
  "error.method_not_allowed": "Unable to perform this operation. Please try again later.",
14
15
  "error.network": "Connection failed. Please check your network settings.",
15
16
  "error.request_too_large": "Unable to perform this operation. Please try again later.",
17
+ "error.request_timeout": "Unable to perform this operation. Please try again later.",
18
+ "error.request_canceled": "Unable to perform this operation. Please try again later.",
16
19
  "error.session_required": "Connection expired. Please sign in again.",
17
20
  "error.unknown_method": "Unable to perform this operation. Please try again later.",
18
21
  "greeting.adminOnly": "Only members of the administrators group can change the greeting.",
@@ -8,11 +8,14 @@
8
8
  "error.bad_request": "Compruebe el valor introducido y vuelva a intentarlo.",
9
9
  "error.bad_response": "No se puede realizar esta operación. Vuelva a intentarlo más tarde.",
10
10
  "error.cross_origin": "No se puede realizar esta operación. Vuelva a intentarlo más tarde.",
11
+ "error.origin_required": "Se rechazó la solicitud: el navegador no indicó que procediera de la página de esta App. Vuelva a abrir la App en un navegador actual.",
11
12
  "error.internal": "No se puede realizar esta operación. Vuelva a intentarlo más tarde.",
12
13
  "error.json_required": "No se puede realizar esta operación. Vuelva a intentarlo más tarde.",
13
14
  "error.method_not_allowed": "No se puede realizar esta operación. Vuelva a intentarlo más tarde.",
14
15
  "error.network": "Error de la conexión. Compruebe su conexión de red.",
15
16
  "error.request_too_large": "No se puede realizar esta operación. Vuelva a intentarlo más tarde.",
17
+ "error.request_timeout": "No se puede realizar esta operación. Vuelva a intentarlo más tarde.",
18
+ "error.request_canceled": "No se puede realizar esta operación. Vuelva a intentarlo más tarde.",
16
19
  "error.session_required": "Conexión caducada. Vuelva a conectarse.",
17
20
  "error.unknown_method": "No se puede realizar esta operación. Vuelva a intentarlo más tarde.",
18
21
  "greeting.adminOnly": "Solo los miembros del grupo administrators pueden cambiar el saludo.",
@@ -8,11 +8,14 @@
8
8
  "error.bad_request": "Vérifiez la valeur saisie et réessayez.",
9
9
  "error.bad_response": "Impossible d'effectuer cette opération. Veuillez réessayer ultérieurement.",
10
10
  "error.cross_origin": "Impossible d'effectuer cette opération. Veuillez réessayer ultérieurement.",
11
+ "error.origin_required": "La requête a été refusée : le navigateur n’a pas indiqué qu’elle provenait de la page de cette App. Rouvrez l’App dans un navigateur récent.",
11
12
  "error.internal": "Impossible d'effectuer cette opération. Veuillez réessayer ultérieurement.",
12
13
  "error.json_required": "Impossible d'effectuer cette opération. Veuillez réessayer ultérieurement.",
13
14
  "error.method_not_allowed": "Impossible d'effectuer cette opération. Veuillez réessayer ultérieurement.",
14
15
  "error.network": "La connexion a échoué. Vérifiez vos paramètres réseau.",
15
16
  "error.request_too_large": "Impossible d'effectuer cette opération. Veuillez réessayer ultérieurement.",
17
+ "error.request_timeout": "Impossible d'effectuer cette opération. Veuillez réessayer ultérieurement.",
18
+ "error.request_canceled": "Impossible d'effectuer cette opération. Veuillez réessayer ultérieurement.",
16
19
  "error.session_required": "La connexion a expiré. Reconnectez-vous.",
17
20
  "error.unknown_method": "Impossible d'effectuer cette opération. Veuillez réessayer ultérieurement.",
18
21
  "greeting.adminOnly": "Seuls les membres du groupe administrators peuvent modifier le message d'accueil.",
@@ -8,11 +8,14 @@
8
8
  "error.bad_request": "Controllare il valore inserito e riprovare.",
9
9
  "error.bad_response": "Impossibile eseguire questa operazione. Riprovare più tardi.",
10
10
  "error.cross_origin": "Impossibile eseguire questa operazione. Riprovare più tardi.",
11
+ "error.origin_required": "La richiesta è stata rifiutata: il browser non ha indicato che proviene dalla pagina di questa App. Riaprire l’App in un browser aggiornato.",
11
12
  "error.internal": "Impossibile eseguire questa operazione. Riprovare più tardi.",
12
13
  "error.json_required": "Impossibile eseguire questa operazione. Riprovare più tardi.",
13
14
  "error.method_not_allowed": "Impossibile eseguire questa operazione. Riprovare più tardi.",
14
15
  "error.network": "Connessione non riuscita. Controllare le impostazioni di rete.",
15
16
  "error.request_too_large": "Impossibile eseguire questa operazione. Riprovare più tardi.",
17
+ "error.request_timeout": "Impossibile eseguire questa operazione. Riprovare più tardi.",
18
+ "error.request_canceled": "Impossibile eseguire questa operazione. Riprovare più tardi.",
16
19
  "error.session_required": "Connessione scaduta. Accedere nuovamente.",
17
20
  "error.unknown_method": "Impossibile eseguire questa operazione. Riprovare più tardi.",
18
21
  "greeting.adminOnly": "Solo i membri del gruppo administrators possono modificare il saluto.",
@@ -8,11 +8,14 @@
8
8
  "error.bad_request": "入力した値を確認して、もう一度お試しください。",
9
9
  "error.bad_response": "この操作は実行できません。後でもう一度お試しください。",
10
10
  "error.cross_origin": "この操作は実行できません。後でもう一度お試しください。",
11
+ "error.origin_required": "リクエストは拒否されました。ブラウザーがこの App のページからのリクエストであることを示しませんでした。最新のブラウザーで App を開き直してください。",
11
12
  "error.internal": "この操作は実行できません。後でもう一度お試しください。",
12
13
  "error.json_required": "この操作は実行できません。後でもう一度お試しください。",
13
14
  "error.method_not_allowed": "この操作は実行できません。後でもう一度お試しください。",
14
15
  "error.network": "接続に失敗しました。ネットワーク設定を確認してください。",
15
16
  "error.request_too_large": "この操作は実行できません。後でもう一度お試しください。",
17
+ "error.request_timeout": "この操作は実行できません。後でもう一度お試しください。",
18
+ "error.request_canceled": "この操作は実行できません。後でもう一度お試しください。",
16
19
  "error.session_required": "接続の期限が切れています。もう 1 度サイン インしてください。",
17
20
  "error.unknown_method": "この操作は実行できません。後でもう一度お試しください。",
18
21
  "greeting.adminOnly": "あいさつを変更できるのは administrators グループのメンバーのみです。",
@@ -8,11 +8,14 @@
8
8
  "error.bad_request": "입력한 값을 확인한 후 다시 시도하십시오.",
9
9
  "error.bad_response": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
10
10
  "error.cross_origin": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
11
+ "error.origin_required": "요청이 거부되었습니다. 브라우저가 이 App 페이지에서 보낸 요청임을 알리지 않았습니다. 최신 브라우저에서 App을 다시 여십시오.",
11
12
  "error.internal": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
12
13
  "error.json_required": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
13
14
  "error.method_not_allowed": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
14
15
  "error.network": "연결 실패. 네트워크 설정을 확인해 주십시오.",
15
16
  "error.request_too_large": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
17
+ "error.request_timeout": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
18
+ "error.request_canceled": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
16
19
  "error.session_required": "연결 만료됨. 다시 로그인 해 주십시오.",
17
20
  "error.unknown_method": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
18
21
  "greeting.adminOnly": "administrators 그룹의 구성원만 인사말을 변경할 수 있습니다.",
@@ -8,11 +8,14 @@
8
8
  "error.bad_request": "请检查输入的值后重试。",
9
9
  "error.bad_response": "无法执行此操作。请稍后再试。",
10
10
  "error.cross_origin": "无法执行此操作。请稍后再试。",
11
+ "error.origin_required": "请求已被拒绝:浏览器没有表明它来自此 App 的页面。请使用新版浏览器重新打开 App。",
11
12
  "error.internal": "无法执行此操作。请稍后再试。",
12
13
  "error.json_required": "无法执行此操作。请稍后再试。",
13
14
  "error.method_not_allowed": "无法执行此操作。请稍后再试。",
14
15
  "error.network": "无法联机,请检查你的网络设置。",
15
16
  "error.request_too_large": "无法执行此操作。请稍后再试。",
17
+ "error.request_timeout": "无法执行此操作。请稍后再试。",
18
+ "error.request_canceled": "无法执行此操作。请稍后再试。",
16
19
  "error.session_required": "联机超时,请重新登录。",
17
20
  "error.unknown_method": "无法执行此操作。请稍后再试。",
18
21
  "greeting.adminOnly": "只有 administrators 群组的成员可以更改问候语。",
@@ -8,11 +8,14 @@
8
8
  "error.bad_request": "請檢查輸入的值後再試一次。",
9
9
  "error.bad_response": "無法執行此操作。請稍後再試。",
10
10
  "error.cross_origin": "無法執行此操作。請稍後再試。",
11
+ "error.origin_required": "要求已被拒絕:瀏覽器沒有表明它來自這個 App 的頁面。請使用新版瀏覽器重新開啟 App。",
11
12
  "error.internal": "無法執行此操作。請稍後再試。",
12
13
  "error.json_required": "無法執行此操作。請稍後再試。",
13
14
  "error.method_not_allowed": "無法執行此操作。請稍後再試。",
14
15
  "error.network": "無法連線,請檢查你的網路設定。",
15
16
  "error.request_too_large": "無法執行此操作。請稍後再試。",
17
+ "error.request_timeout": "無法執行此操作。請稍後再試。",
18
+ "error.request_canceled": "無法執行此操作。請稍後再試。",
16
19
  "error.session_required": "連線逾時。請重新登入。",
17
20
  "error.unknown_method": "無法執行此操作。請稍後再試。",
18
21
  "greeting.adminOnly": "只有 administrators 群組的成員可以變更問候語。",