@synopackageland/templates 0.9.7 → 0.9.8
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/README.md +1 -1
- package/package.json +1 -1
- package/templates/native-go/backend/THIRD_PARTY_NOTICES.txt +1 -1
- package/templates/native-go/backend/go.mod +1 -1
- package/templates/native-go/backend/go.sum +2 -2
- package/templates/native-go/backend/internal/app/background_test.go +1 -0
- package/templates/native-go/backend/internal/app/routes_test.go +2 -0
- 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-delegate/frontend/src/CapabilityPanels.vue +8 -6
- package/templates/native-go-delegate/frontend/test/capability-panels.test.ts +28 -2
- package/templates/native-go-jobs/backend/internal/app/background_routes_test.go +1 -0
- package/templates/native-go-jobs/frontend/test/background-page.test.ts +18 -0
- package/templates/native-go-vue/frontend/package-lock.json +12 -12
- package/templates/native-go-vue/frontend/package.json +2 -2
- package/templates/native-go-vue/frontend/src/App.vue +14 -7
- package/templates/native-go-vue/frontend/src/CapabilityPanels.vue +4 -1
- package/templates/native-go-vue/frontend/src/i18n/de.json +0 -1
- package/templates/native-go-vue/frontend/src/i18n/en.json +0 -1
- package/templates/native-go-vue/frontend/src/i18n/es.json +0 -1
- package/templates/native-go-vue/frontend/src/i18n/fr.json +0 -1
- package/templates/native-go-vue/frontend/src/i18n/it.json +0 -1
- package/templates/native-go-vue/frontend/src/i18n/ja.json +0 -1
- package/templates/native-go-vue/frontend/src/i18n/ko.json +0 -1
- package/templates/native-go-vue/frontend/src/i18n/zh-CN.json +0 -1
- package/templates/native-go-vue/frontend/src/i18n/zh-TW.json +0 -1
- package/templates/native-go-vue/frontend/test/apps.test.ts +37 -0
- package/templates/native-go-vue/frontend/test/native-controls.test.ts +4 -4
- package/templates/native-rust/backend/Cargo.lock +2 -2
- package/templates/native-rust/backend/Cargo.toml +1 -1
- package/templates/native-rust/backend/THIRD_PARTY_NOTICES.txt +1 -1
- package/templates/native-rust/backend/src/app/test_support.rs +2 -0
- package/templates/native-rust/i18n/messages/de.json +1 -0
- package/templates/native-rust/i18n/messages/en.json +1 -0
- package/templates/native-rust/i18n/messages/es.json +1 -0
- package/templates/native-rust/i18n/messages/fr.json +1 -0
- package/templates/native-rust/i18n/messages/it.json +1 -0
- package/templates/native-rust/i18n/messages/ja.json +1 -0
- package/templates/native-rust/i18n/messages/ko.json +1 -0
- package/templates/native-rust/i18n/messages/zh-CN.json +1 -0
- package/templates/native-rust/i18n/messages/zh-TW.json +1 -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.
|
|
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.
|
|
3
|
+
"version": "0.9.8",
|
|
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,4 +1,4 @@
|
|
|
1
|
-
github.com/synopackageland/syno-pkg-land-sdk v0.9.
|
|
2
|
-
github.com/synopackageland/syno-pkg-land-sdk v0.9.
|
|
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
|
|
|
@@ -8,6 +8,7 @@
|
|
|
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.",
|
|
@@ -8,6 +8,7 @@
|
|
|
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.",
|
|
@@ -8,6 +8,7 @@
|
|
|
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.",
|
|
@@ -8,6 +8,7 @@
|
|
|
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.",
|
|
@@ -8,6 +8,7 @@
|
|
|
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.",
|
|
@@ -8,6 +8,7 @@
|
|
|
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": "この操作は実行できません。後でもう一度お試しください。",
|
|
@@ -8,6 +8,7 @@
|
|
|
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": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
|
|
@@ -8,6 +8,7 @@
|
|
|
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": "无法执行此操作。请稍后再试。",
|
|
@@ -8,6 +8,7 @@
|
|
|
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": "無法執行此操作。請稍後再試。",
|
|
@@ -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
|
-
<
|
|
31
|
-
<Callout v-else tone="danger" role="alert">{{ copy.error }} <Button variant="secondary" @click="detect">{{ copy.
|
|
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
|
|
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.
|
|
11
|
-
"@synopackageland/ui": "0.9.
|
|
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.
|
|
1152
|
-
"resolved": "https://registry.npmjs.org/@synopackageland/sdk/-/sdk-0.9.
|
|
1153
|
-
"integrity": "sha512-
|
|
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.
|
|
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.
|
|
1171
|
-
"resolved": "https://registry.npmjs.org/@synopackageland/ui/-/ui-0.9.
|
|
1172
|
-
"integrity": "sha512-
|
|
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.
|
|
2542
|
-
"resolved": "https://registry.npmjs.org/zod/-/zod-4.6.
|
|
2543
|
-
"integrity": "sha512-
|
|
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"
|
|
@@ -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="
|
|
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="
|
|
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": "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.",
|
|
@@ -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
|
-
//
|
|
14
|
-
//
|
|
15
|
-
test('FormItem controls
|
|
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
|
|
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.
|
|
108
|
-
source = "git+https://github.com/synopackageland/syno-pkg-land-sdk?tag=v0.9.
|
|
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.
|
|
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.
|
|
@@ -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
|
|
|
@@ -8,6 +8,7 @@
|
|
|
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.",
|
|
@@ -8,6 +8,7 @@
|
|
|
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.",
|
|
@@ -8,6 +8,7 @@
|
|
|
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.",
|
|
@@ -8,6 +8,7 @@
|
|
|
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.",
|
|
@@ -8,6 +8,7 @@
|
|
|
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.",
|
|
@@ -8,6 +8,7 @@
|
|
|
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": "この操作は実行できません。後でもう一度お試しください。",
|
|
@@ -8,6 +8,7 @@
|
|
|
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": "이 작업을 수행할 수 없습니다. 나중에 다시 시도하십시오.",
|
|
@@ -8,6 +8,7 @@
|
|
|
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": "无法执行此操作。请稍后再试。",
|
|
@@ -8,6 +8,7 @@
|
|
|
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": "無法執行此操作。請稍後再試。",
|