@synopackageland/templates 0.35.5 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -17
- package/package.json +1 -1
- package/templates/compose/dsm-package.yaml +1 -1
- package/templates/native-go/README.md +2 -2
- 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.go +10 -12
- package/templates/native-go/backend/internal/app/background_inbox_test.go +37 -0
- package/templates/native-go/backend/internal/app/background_owner.go +14 -0
- package/templates/native-go/backend/internal/app/background_owner_test.go +2 -2
- package/templates/native-go/backend/internal/app/background_test.go +27 -8
- package/templates/native-go-delegate/frontend/src/CapabilityPanels.vue +1 -1
- package/templates/native-go-delegate/frontend/test/capability-panels.test.ts +1 -0
- package/templates/native-go-vue/frontend/package-lock.json +658 -9
- package/templates/native-go-vue/frontend/package.json +2 -2
- package/templates/native-node/README.md +2 -2
- package/templates/native-node/backend/package-lock.json +2 -2
- package/templates/native-node/dsm-package.yaml +1 -1
- package/templates/native-node-delegate/backend/src/delegate.ts +20 -8
- package/templates/native-node-delegate/backend/test/delegate.test.ts +18 -9
- package/templates/native-python/README.md +2 -2
- package/templates/native-python/dsm-package.yaml +1 -1
- package/templates/native-python-delegate/backend/app/delegate.py +19 -7
- package/templates/native-python-delegate/backend/tests/test_delegate.py +18 -8
- package/templates/native-rust/README.md +2 -2
- package/templates/native-rust/backend/Cargo.lock +2 -2
- package/templates/native-rust/backend/Cargo.toml +1 -1
- package/templates/native-rust-delegate/backend/Cargo.lock +203 -0
- package/templates/native-rust-delegate/backend/Cargo.toml +17 -0
- package/templates/native-rust-delegate/backend/src/delegate.rs +78 -23
package/README.md
CHANGED
|
@@ -22,8 +22,8 @@ daemon:
|
|
|
22
22
|
window_seconds: 300
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Templates 0.
|
|
26
|
-
Use CLI
|
|
25
|
+
Templates 0.37.0 pin UI 0.19.1 and SDK 0.41.1 in native and Compose projects.
|
|
26
|
+
Use a CLI that supports executor manifest format 1. The Vue `AppShell` shows a
|
|
27
27
|
non-blocking **Reload** notice when an upgrade replaced the UI an open page
|
|
28
28
|
loaded. Go Apps select
|
|
29
29
|
their controller from their Go SDK requirement; an identity gateway selects it
|
|
@@ -33,8 +33,9 @@ override does not waive that version check.
|
|
|
33
33
|
|
|
34
34
|
The SDK owns backoff, the sliding retry cap and stop cancellation. Keep existing
|
|
35
35
|
stop-before-upgrade/uninstall hooks; do not add retry loops in App scripts.
|
|
36
|
-
Clean exits do not restart;
|
|
37
|
-
|
|
36
|
+
Clean exits do not restart; failures before the first successful startup use
|
|
37
|
+
the same bounded recovery policy as later exits. Compose keeps its own supervision.
|
|
38
|
+
An App backend may map SDK
|
|
38
39
|
`status-json` state/reason to its authenticated status UI; no new status panel is
|
|
39
40
|
generated. See the CLI's [daemon policy guide](https://github.com/synopackageland/syno-pkg-land-cli/blob/main/docs/daemon.md)
|
|
40
41
|
for declaration bounds, SDK selection, build/inspect checks and diagnostics.
|
|
@@ -81,20 +82,21 @@ calls each one from the generated script of the same name (`synopkgland skill sh
|
|
|
81
82
|
The placeholders `__PACKAGE__` (also the Go module path), `__DISPLAY_NAME__` and
|
|
82
83
|
`__BACKGROUND_ENABLED__` are substituted by `init`.
|
|
83
84
|
|
|
84
|
-
`backend/go.mod` requires `github.com/synopackageland/syno-pkg-land-sdk v0.
|
|
85
|
+
`backend/go.mod` requires `github.com/synopackageland/syno-pkg-land-sdk v0.41.1`; that line is
|
|
85
86
|
the single source of the SDK version and `frontend/package.json` pins
|
|
86
87
|
`@synopackageland/sdk` to the same version. Python and Node.js name it in
|
|
87
88
|
`backend.sdk`, Rust in the `syno-pkg-land-app` tag of `backend/Cargo.toml`
|
|
88
|
-
(and `Cargo.lock`); all of them pin 0.
|
|
89
|
-
`@synopackageland/ui` 0.
|
|
89
|
+
(and `Cargo.lock`); all of them pin 0.41.1. The Vue frontend pins
|
|
90
|
+
`@synopackageland/ui` 0.19.1. SDK 0.41.1 accepts UI `>=0.10.1 <0.20.0`,
|
|
90
91
|
so no UI peer override is needed. Tests use jsdom 30 for UI floating-layer styles.
|
|
91
92
|
|
|
92
93
|
Interactive NAS file operations use same-origin browser calls.
|
|
93
94
|
Custom file handlers use the SDK's
|
|
94
|
-
[session transport migration](https://github.com/synopackageland/syno-pkg-land-sdk/blob/v0.
|
|
95
|
-
CLI
|
|
96
|
-
executor/v0.
|
|
97
|
-
|
|
95
|
+
[session transport migration](https://github.com/synopackageland/syno-pkg-land-sdk/blob/v0.41.1/docs/dsm-session-transport.md).
|
|
96
|
+
The CLI reads SDK 0.41.1's release manifest, which names immutable
|
|
97
|
+
executor/v0.39.0 and its executor digests. CLI distributions must resolve
|
|
98
|
+
templates 0.37.x before default scaffolds use this release; a `~0.36.0`
|
|
99
|
+
templates dependency remains on the older minor.
|
|
98
100
|
|
|
99
101
|
Every App method must appear in the permissions table, including methods whose
|
|
100
102
|
permission equals `default: administrators`. Dynamic method names and custom
|
|
@@ -108,7 +110,7 @@ Keep punctuation between sentences and intentional loading ellipses.
|
|
|
108
110
|
|
|
109
111
|
## Request deadlines and background work
|
|
110
112
|
|
|
111
|
-
SDK/App 0.
|
|
113
|
+
SDK/App 0.41.1 defaults to 300 seconds for cooperative requests and 302 seconds
|
|
112
114
|
for the TS client and generated DSM window. The CLI reads the selected SDK's
|
|
113
115
|
timeout spec; `backend.timeout_seconds` explicitly overrides it for native
|
|
114
116
|
backends, and an earlier parent deadline still wins. Go assembly should fall
|
|
@@ -140,7 +142,7 @@ caps, approved root policy/program budgets and credential lifetimes retain
|
|
|
140
142
|
their existing limits; long root work uses `root.start` / `root.jobs.*`.
|
|
141
143
|
The independent ctl transport keeps its 62-second default: use an explicit
|
|
142
144
|
`--timeout` for long synchronous calls, or an existing nonblocking job flow.
|
|
143
|
-
See the SDK's [request deadline contract](https://github.com/synopackageland/syno-pkg-land-sdk/blob/v0.
|
|
145
|
+
See the SDK's [request deadline contract](https://github.com/synopackageland/syno-pkg-land-sdk/blob/v0.41.1/docs/request-deadlines.md).
|
|
144
146
|
|
|
145
147
|
## Hyper Backup
|
|
146
148
|
|
|
@@ -286,9 +288,12 @@ Native Node Apps use the official [`Node.js_v22`](https://www.synology.com/en-us
|
|
|
286
288
|
|
|
287
289
|
The jobs overlays submit files the App's own producer has already imported into
|
|
288
290
|
its private data directory. Submit `{key, name, input}` through `bg.jobs.submit`
|
|
289
|
-
(Go) or `hash.submit` (Python, Node.js and Rust)
|
|
290
|
-
|
|
291
|
-
|
|
291
|
+
(Go) or `hash.submit` (Python, Node.js and Rust). In Go, a trusted producer writes
|
|
292
|
+
into `BackgroundInbox(dataDir, background.Submitter{User: verifiedName, UID: verifiedUID})`;
|
|
293
|
+
`input` must be an absolute path inside that exact owner's inbox, including for
|
|
294
|
+
administrators. The name and UID both scope the inbox, so a recreated account
|
|
295
|
+
gets another directory. Symlinks cannot escape it. Jobs and schedules retain a
|
|
296
|
+
bounded private copy. Bridge-language job producers retain their SDK input contract.
|
|
292
297
|
There is no NAS browse/import example or automatic file submission in either UI shell.
|
|
293
298
|
|
|
294
299
|
Keep the job controls and package-account workers for progress, cancellation and
|
|
@@ -299,7 +304,7 @@ The bridge examples expose submission, lookup and cancellation through
|
|
|
299
304
|
|
|
300
305
|
## External access
|
|
301
306
|
|
|
302
|
-
With CLI
|
|
307
|
+
With a compatible CLI and SDK 0.41.1, a gateway App may declare `identity_gateway.external_access: {connect: optional, origin_change: confirm, single_origin: true}` for an upstream that signs in on one fixed URL. The policy is forwarded to the packaged gateway. A single-origin App's `manual_origin` accepts an explicit HTTPS port; the `external-origin` installation setting remains restricted to port 443. The DSM launcher selects a provider destination, probes `/_spl/ping` for up to eight seconds, and explains failures before offering **Open anyway**. A configured single-origin App always opens its primary external origin.
|
|
303
308
|
|
|
304
309
|
|
|
305
310
|
Compose templates declare an app-named dedicated shared folder by default. DSM
|
|
@@ -332,3 +337,15 @@ marker with their own. Use `entry` only for a configured entry override. An
|
|
|
332
337
|
entry declared upstream-authenticated in the manifest also needs `publicLogin`
|
|
333
338
|
to identify its own login page. A DSM-gated entry cannot opt out of the gate.
|
|
334
339
|
This smoke checks opening only; it does not install packages or change accounts.
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
The delegate report example scopes staging to
|
|
343
|
+
`owners/<sha256(username + NUL + delegationID)>/` and scopes its executor job ID
|
|
344
|
+
to SHA-256 of username, NUL, delegation ID, NUL and its producer job ID, followed
|
|
345
|
+
by a hyphen and the first 32 hex characters of the owner hash. The SDK verifies
|
|
346
|
+
this owner-and-grant suffix on admission. Repeated
|
|
347
|
+
saves by the same owner and grant reuse their original file; other owners and
|
|
348
|
+
grants get different sources. A newly created report refused with
|
|
349
|
+
`DELEGATION_DENIED` is removed. An ambiguous transport failure keeps the source
|
|
350
|
+
for retry because the executor may already have admitted it. Other settled
|
|
351
|
+
sources use the SDK's explicit discard operation when the App deletes the job.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synopackageland/templates",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "Project templates for synopkgland init: native-go, native-python, native-node and native-rust (plain or Vue, with jobs, delegate and root-ops overlays), binary and compose.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -23,7 +23,7 @@ compose:
|
|
|
23
23
|
web: {service: web, port: 8080, health: {path: /, status: 200}}
|
|
24
24
|
network: {egress: isolated}
|
|
25
25
|
identity_gateway:
|
|
26
|
-
sdk: 0.
|
|
26
|
+
sdk: 0.41.1
|
|
27
27
|
mode: none
|
|
28
28
|
listen: {http: 28000}
|
|
29
29
|
# Web Apps default to new-page through the shared app-launch resolver.
|
|
@@ -17,7 +17,7 @@ DSM main menu → DSM window (ui/app.js)
|
|
|
17
17
|
|
|
18
18
|
## Requests and long work
|
|
19
19
|
|
|
20
|
-
SDK/App 0.
|
|
20
|
+
SDK/App 0.41.1 defaults to 300-second requests and a 302-second browser wait.
|
|
21
21
|
Explicit `backend.timeout_seconds` and earlier parent deadlines still apply;
|
|
22
22
|
rebuild the App to use the new SDK. For long NAS work, enable the existing jobs
|
|
23
23
|
capability: return admission and the original job ID promptly, execute in the
|
|
@@ -27,7 +27,7 @@ last queued/running state intact. After a lost admission reply, reuse the same
|
|
|
27
27
|
idempotency key and input instead of replaying a mutation with a new key.
|
|
28
28
|
Jobs default to 30 minutes; approved executor limits remain separate. ctl keeps
|
|
29
29
|
its 62-second transport default; use an explicit `--timeout` or an existing
|
|
30
|
-
nonblocking job. See the SDK's [request deadline contract](https://github.com/synopackageland/syno-pkg-land-sdk/blob/v0.
|
|
30
|
+
nonblocking job. See the SDK's [request deadline contract](https://github.com/synopackageland/syno-pkg-land-sdk/blob/v0.41.1/docs/request-deadlines.md).
|
|
31
31
|
|
|
32
32
|
## Quick start
|
|
33
33
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
github.com/synopackageland/syno-pkg-land-sdk v0.
|
|
2
|
-
github.com/synopackageland/syno-pkg-land-sdk v0.
|
|
1
|
+
github.com/synopackageland/syno-pkg-land-sdk v0.41.1 h1:DnNgIOz08eG9xh1rJjGreHkGdba6ABFtxxG+7PgWMzs=
|
|
2
|
+
github.com/synopackageland/syno-pkg-land-sdk v0.41.1/go.mod h1:8GwREGbczc8mAk4Gv8FUHaukXXCUstjzkovQrNV59Cc=
|
|
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=
|
|
@@ -10,7 +10,6 @@ import (
|
|
|
10
10
|
"os"
|
|
11
11
|
"os/user"
|
|
12
12
|
"path/filepath"
|
|
13
|
-
"strings"
|
|
14
13
|
"syscall"
|
|
15
14
|
"time"
|
|
16
15
|
|
|
@@ -125,7 +124,7 @@ func (b backgroundAPI) submit(ctx context.Context, _ api.Caller, _ *http.Request
|
|
|
125
124
|
if !access.Owner.Valid() {
|
|
126
125
|
return nil, &api.Error{Code: "identity_unavailable", Message: "DSM user UID could not be resolved"}
|
|
127
126
|
}
|
|
128
|
-
data, err := b.readInput(in.Input)
|
|
127
|
+
data, err := b.readInput(in.Input, access.Owner)
|
|
129
128
|
if err != nil {
|
|
130
129
|
return nil, err
|
|
131
130
|
}
|
|
@@ -138,20 +137,19 @@ func (b backgroundAPI) submit(ctx context.Context, _ api.Caller, _ *http.Request
|
|
|
138
137
|
return map[string]any{"job": job, "duplicate": duplicate}, nil
|
|
139
138
|
}
|
|
140
139
|
|
|
141
|
-
// readInput
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if err != nil || !filepath.IsAbs(input) || !filepath.IsLocal(rel) {
|
|
140
|
+
// readInput opens only the authenticated owner's producer inbox. os.Root
|
|
141
|
+
// confines symlinks as well as lexical paths to that inbox, including for admins.
|
|
142
|
+
func (b backgroundAPI) readInput(input string, owner background.Submitter) ([]byte, error) {
|
|
143
|
+
bad := api.BadRequest("input must be a regular file in your App inbox")
|
|
144
|
+
inbox, err := BackgroundInbox(b.dataDir, owner)
|
|
145
|
+
if err != nil {
|
|
148
146
|
return nil, bad
|
|
149
147
|
}
|
|
150
|
-
|
|
151
|
-
if
|
|
148
|
+
rel, err := filepath.Rel(inbox, input)
|
|
149
|
+
if err != nil || !filepath.IsAbs(input) || !filepath.IsLocal(rel) {
|
|
152
150
|
return nil, bad
|
|
153
151
|
}
|
|
154
|
-
root, err := os.OpenRoot(
|
|
152
|
+
root, err := os.OpenRoot(inbox)
|
|
155
153
|
if err != nil {
|
|
156
154
|
return nil, bad
|
|
157
155
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package app
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"testing"
|
|
8
|
+
|
|
9
|
+
"github.com/synopackageland/syno-pkg-land-sdk/background"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
func TestBackgroundSubmitRefusesOtherOwnerInboxAndAppFiles(t *testing.T) {
|
|
13
|
+
e := ordinaryEnv(t)
|
|
14
|
+
private := filepath.Join(e.dataDir, "private-settings")
|
|
15
|
+
if err := os.WriteFile(private, []byte("private"), 0600); err != nil {
|
|
16
|
+
t.Fatal(err)
|
|
17
|
+
}
|
|
18
|
+
for _, input := range []string{private, e.inputPath("alice")} {
|
|
19
|
+
p, _ := json.Marshal(map[string]string{"key": "stolen", "name": "copy", "input": input})
|
|
20
|
+
if code, _ := e.call(bob, "bg.jobs.submit", string(p)); code != "bad_request" {
|
|
21
|
+
t.Fatal("cross-owner import accepted", code)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
own := e.inputPath("bob")
|
|
25
|
+
if err := os.Symlink(private, filepath.Join(filepath.Dir(own), "escape")); err != nil {
|
|
26
|
+
t.Fatal(err)
|
|
27
|
+
}
|
|
28
|
+
p, _ := json.Marshal(map[string]string{"key": "link", "name": "copy", "input": filepath.Join(filepath.Dir(own), "escape")})
|
|
29
|
+
if code, _ := e.call(bob, "bg.jobs.submit", string(p)); code != "bad_request" {
|
|
30
|
+
t.Fatal("inbox symlink escaped", code)
|
|
31
|
+
}
|
|
32
|
+
old, _ := BackgroundInbox(e.dataDir, background.Submitter{User: "bob", UID: "1025"})
|
|
33
|
+
current, _ := BackgroundInbox(e.dataDir, background.Submitter{User: "bob", UID: "2048"})
|
|
34
|
+
if old == current {
|
|
35
|
+
t.Fatal("recreated user retained its inbox")
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -2,10 +2,13 @@ package app
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"context"
|
|
5
|
+
"crypto/sha256"
|
|
6
|
+
"encoding/hex"
|
|
5
7
|
"encoding/json"
|
|
6
8
|
"github.com/synopackageland/syno-pkg-land-sdk/api"
|
|
7
9
|
"github.com/synopackageland/syno-pkg-land-sdk/background"
|
|
8
10
|
"net/http"
|
|
11
|
+
"path/filepath"
|
|
9
12
|
)
|
|
10
13
|
|
|
11
14
|
type backgroundAccessKey struct{}
|
|
@@ -36,3 +39,14 @@ func (b backgroundAPI) purge(ctx context.Context, c api.Caller, _ *http.Request,
|
|
|
36
39
|
}
|
|
37
40
|
return audit, nil
|
|
38
41
|
}
|
|
42
|
+
|
|
43
|
+
// BackgroundInbox is the private input directory a trusted App producer fills
|
|
44
|
+
// for an authenticated owner. Both account name and UID are part of its key.
|
|
45
|
+
func BackgroundInbox(dataDir string, owner background.Submitter) (string, error) {
|
|
46
|
+
if !owner.Valid() {
|
|
47
|
+
return "", api.BadRequest("exact owner is required")
|
|
48
|
+
}
|
|
49
|
+
raw, _ := json.Marshal([2]string{owner.User, owner.UID})
|
|
50
|
+
key := sha256.Sum256(raw)
|
|
51
|
+
return filepath.Join(dataDir, "inbox", hex.EncodeToString(key[:])), nil
|
|
52
|
+
}
|
|
@@ -28,7 +28,7 @@ var carol = stubAuth{identity: api.Identity{User: "carol"}}
|
|
|
28
28
|
|
|
29
29
|
func ownerSubmit(t *testing.T, e *bgEnv, a api.Authenticator, key string) string {
|
|
30
30
|
t.Helper()
|
|
31
|
-
code, out := e.call(a, "bg.jobs.submit", e.submitParams(key))
|
|
31
|
+
code, out := e.call(a, "bg.jobs.submit", e.submitParams(key, a.(stubAuth).identity.User))
|
|
32
32
|
if code != "" {
|
|
33
33
|
t.Fatalf("submit %s: %s %v", key, code, out)
|
|
34
34
|
}
|
|
@@ -103,7 +103,7 @@ func TestBackgroundPersonalQuotaAndAdminPurge(t *testing.T) {
|
|
|
103
103
|
}
|
|
104
104
|
ownerSubmit(t, e, bob, "one")
|
|
105
105
|
ownerSubmit(t, e, bob, "two")
|
|
106
|
-
if code, _ := e.call(bob, "bg.jobs.submit", e.submitParams("three")); code != "owner-queue-full" {
|
|
106
|
+
if code, _ := e.call(bob, "bg.jobs.submit", e.submitParams("three", "bob")); code != "owner-queue-full" {
|
|
107
107
|
t.Fatal(code)
|
|
108
108
|
}
|
|
109
109
|
ownerSubmit(t, e, carol, "one")
|
|
@@ -28,10 +28,9 @@ func newBgEnv(t *testing.T) *bgEnv {
|
|
|
28
28
|
t.Cleanup(func() { lookupSubmitterUID = osLookupUID })
|
|
29
29
|
dir := t.TempDir()
|
|
30
30
|
store := background.NewStore(filepath.Join(dir, "background"))
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return &bgEnv{t: t, store: store, dataDir: dir, srv: map[string]*api.Server{}}
|
|
31
|
+
e := &bgEnv{t: t, store: store, dataDir: dir, srv: map[string]*api.Server{}}
|
|
32
|
+
e.inputPath("alice")
|
|
33
|
+
return e
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
func (e *bgEnv) call(auth api.Authenticator, method, params string) (string, map[string]any) {
|
|
@@ -128,8 +127,28 @@ func TestBackgroundStatusReportsLimitsAndWorker(t *testing.T) {
|
|
|
128
127
|
}
|
|
129
128
|
}
|
|
130
129
|
|
|
131
|
-
func (e *bgEnv)
|
|
132
|
-
|
|
130
|
+
func (e *bgEnv) inputPath(user string) string {
|
|
131
|
+
inbox, err := BackgroundInbox(e.dataDir, background.Submitter{User: user, UID: lookupSubmitterUID(user)})
|
|
132
|
+
if err != nil {
|
|
133
|
+
e.t.Fatal(err)
|
|
134
|
+
}
|
|
135
|
+
if err = os.MkdirAll(inbox, 0700); err != nil {
|
|
136
|
+
e.t.Fatal(err)
|
|
137
|
+
}
|
|
138
|
+
input := filepath.Join(inbox, "input.txt")
|
|
139
|
+
if _, err = os.Stat(input); os.IsNotExist(err) {
|
|
140
|
+
if err = os.WriteFile(input, []byte("hello"), 0600); err != nil {
|
|
141
|
+
e.t.Fatal(err)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return input
|
|
145
|
+
}
|
|
146
|
+
func (e *bgEnv) submitParams(key string, users ...string) string {
|
|
147
|
+
user := "alice"
|
|
148
|
+
if len(users) > 0 {
|
|
149
|
+
user = users[0]
|
|
150
|
+
}
|
|
151
|
+
p, err := json.Marshal(map[string]string{"key": key, "name": "a.txt", "input": e.inputPath(user)})
|
|
133
152
|
if err != nil {
|
|
134
153
|
e.t.Fatal(err)
|
|
135
154
|
}
|
|
@@ -155,7 +174,7 @@ func TestBackgroundSubmitImportsAppInputAndIsIdempotent(t *testing.T) {
|
|
|
155
174
|
if data(again)["duplicate"] != true || data(again)["job"].(map[string]any)["id"] != job["id"] {
|
|
156
175
|
t.Fatal(again)
|
|
157
176
|
}
|
|
158
|
-
if err := os.WriteFile(
|
|
177
|
+
if err := os.WriteFile(e.inputPath("alice"), []byte("other"), 0600); err != nil {
|
|
159
178
|
t.Fatal(err)
|
|
160
179
|
}
|
|
161
180
|
if code, _ := e.call(alice, "bg.jobs.submit", params); code != "conflict" {
|
|
@@ -189,7 +208,7 @@ func TestBackgroundSubmitRejectsExternalAndOversizedInput(t *testing.T) {
|
|
|
189
208
|
t.Fatalf("%s: %s", input, code)
|
|
190
209
|
}
|
|
191
210
|
}
|
|
192
|
-
if err := os.WriteFile(
|
|
211
|
+
if err := os.WriteFile(e.inputPath("alice"), make([]byte, background.MaxInputBytes+1), 0600); err != nil {
|
|
193
212
|
t.Fatal(err)
|
|
194
213
|
}
|
|
195
214
|
if code, _ := e.call(alice, "bg.jobs.submit", e.submitParams("big")); code != "limit-exceeded" {
|
|
@@ -30,7 +30,7 @@ onBeforeUnmount(()=>{disposed=true;provider.dispose()})
|
|
|
30
30
|
<BackgroundSavePanel v-if="mode==='long'" ref="panel" :key="revision" :sdk="sdk" :admin="admin" :locale="ctx.locale" :picker="{provider,keys}" sample-save />
|
|
31
31
|
<DelegatePanel v-else-if="mode==='legacy'" ref="panel" :ctx="ctx" />
|
|
32
32
|
<SectionSkeleton v-else-if="mode==='loading'" />
|
|
33
|
-
<Callout v-else tone="danger" role="alert">
|
|
33
|
+
<Callout v-else tone="danger" role="alert" :dismissible="false">
|
|
34
34
|
{{ copy.error }}
|
|
35
35
|
<template #actions><Button variant="secondary" @click="detect">{{ copy.retry }}</Button></template>
|
|
36
36
|
</Callout>
|
|
@@ -136,6 +136,7 @@ test('a failed status check offers Retry as a Callout action and recovers',async
|
|
|
136
136
|
const retry=button(root,'Retry')
|
|
137
137
|
expect(alert.querySelector('[data-ui-callout-actions]')?.contains(retry)).toBe(true)
|
|
138
138
|
expect(alert.querySelector('[data-ui-callout-body]')?.querySelector('button')).toBeNull()
|
|
139
|
+
expect(alert.querySelector('[data-ui-callout-close]')).toBeNull()
|
|
139
140
|
retry.click();await settle()
|
|
140
141
|
expect(attempts).toBe(2)
|
|
141
142
|
expect(root.querySelector('[role="alert"]')).toBeNull()
|