@synopackageland/templates 0.9.8 → 0.10.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 +27 -27
- package/package.json +2 -2
- package/templates/_shared/AGENTS.md +53 -15
- package/templates/native-go/backend/THIRD_PARTY_NOTICES.txt +1 -1
- package/templates/native-go/backend/cmd/app/main.go +15 -1
- package/templates/native-go/backend/cmd/app/main_test.go +33 -0
- package/templates/native-go/backend/go.mod +1 -1
- package/templates/native-go/backend/go.sum +2 -2
- package/templates/native-go/i18n/glossary.json +3 -1
- package/templates/native-go/i18n/messages/de.json +6 -0
- package/templates/native-go/i18n/messages/en.json +6 -0
- package/templates/native-go/i18n/messages/es.json +6 -0
- package/templates/native-go/i18n/messages/fr.json +6 -0
- package/templates/native-go/i18n/messages/it.json +6 -0
- package/templates/native-go/i18n/messages/ja.json +6 -0
- package/templates/native-go/i18n/messages/ko.json +6 -0
- package/templates/native-go/i18n/messages/zh-CN.json +6 -0
- package/templates/native-go/i18n/messages/zh-TW.json +6 -0
- package/templates/native-go/ui/app.js +2 -1
- package/templates/native-go-delegate/frontend/test/capability-panels.test.ts +3 -2
- package/templates/native-go-jobs/frontend/test/background-page.test.ts +4 -4
- package/templates/native-go-vue/README.md +19 -7
- package/templates/native-go-vue/frontend/package-lock.json +9 -9
- package/templates/native-go-vue/frontend/package.json +2 -2
- package/templates/native-go-vue/frontend/src/App.vue +31 -36
- package/templates/native-go-vue/frontend/test/apps.test.ts +65 -12
- package/templates/native-go-vue/frontend/test/context.ts +8 -0
- package/templates/native-go-vue/frontend/test/native-controls.test.ts +3 -4
- package/templates/native-go-vue/frontend/test/shell-i18n.test.ts +2 -2
- package/templates/native-node/README.md +153 -0
- package/templates/native-node/backend/app/package.json +4 -0
- package/templates/native-node/backend/app/permissions.json +5 -0
- package/templates/native-node/backend/package-lock.json +59 -0
- package/templates/native-node/backend/package.json +17 -0
- package/templates/native-node/backend/src/delegate.ts +5 -0
- package/templates/native-node/backend/src/jobs.ts +5 -0
- package/templates/native-node/backend/src/main.ts +4 -0
- package/templates/native-node/backend/src/params.ts +11 -0
- package/templates/native-node/backend/src/root-ops.ts +5 -0
- package/templates/native-node/backend/src/routes.ts +97 -0
- package/templates/native-node/backend/src/service.ts +22 -0
- package/templates/native-node/backend/test/routes.test.ts +75 -0
- package/templates/native-node/backend/test/support.ts +67 -0
- package/templates/native-node/backend/tsconfig.build.json +8 -0
- package/templates/native-node/backend/tsconfig.json +18 -0
- package/templates/native-node/dsm-package.yaml +80 -0
- package/templates/native-node/gitignore +4 -0
- package/templates/native-node-delegate/backend/src/delegate.ts +35 -0
- package/templates/native-node-delegate/backend/test/delegate.test.ts +51 -0
- package/templates/native-node-jobs/backend/src/jobs.ts +38 -0
- package/templates/native-node-jobs/backend/test/jobs.test.ts +73 -0
- package/templates/native-node-root-ops/backend/src/root-ops.ts +16 -0
- package/templates/native-node-root-ops/backend/test/root-ops.test.ts +23 -0
- package/templates/native-node-vue/README.md +56 -0
- package/templates/native-node-vue/gitignore +4 -0
- package/templates/native-python/README.md +150 -0
- package/templates/native-python/backend/app/delegate.py +5 -0
- package/templates/native-python/backend/app/jobs.py +5 -0
- package/templates/native-python/backend/app/main.py +5 -0
- package/templates/native-python/backend/app/params.py +10 -0
- package/templates/native-python/backend/app/permissions.json +5 -0
- package/templates/native-python/backend/app/root_ops.py +5 -0
- package/templates/native-python/backend/app/routes.py +107 -0
- package/templates/native-python/backend/tests/support.py +72 -0
- package/templates/native-python/backend/tests/test_routes.py +73 -0
- package/templates/native-python/dsm-package.yaml +77 -0
- package/templates/native-python/gitignore +2 -0
- package/templates/native-python-delegate/backend/app/delegate.py +42 -0
- package/templates/native-python-delegate/backend/tests/test_delegate.py +58 -0
- package/templates/native-python-jobs/backend/app/jobs.py +40 -0
- package/templates/native-python-jobs/backend/tests/test_jobs.py +72 -0
- package/templates/native-python-root-ops/backend/app/root_ops.py +18 -0
- package/templates/native-python-root-ops/backend/tests/test_root_ops.py +34 -0
- package/templates/native-python-vue/README.md +56 -0
- package/templates/native-python-vue/gitignore +3 -0
- package/templates/native-rust/README.md +74 -296
- package/templates/native-rust/backend/Cargo.lock +6 -6
- package/templates/native-rust/backend/Cargo.toml +2 -10
- package/templates/native-rust/backend/src/delegate.rs +6 -0
- package/templates/native-rust/backend/src/jobs.rs +6 -0
- package/templates/native-rust/backend/src/main.rs +12 -0
- package/templates/native-rust/backend/src/root_ops.rs +6 -0
- package/templates/native-rust/backend/src/routes.rs +352 -0
- package/templates/native-rust/backend/src/test_support.rs +91 -0
- package/templates/native-rust/dsm-package.yaml +10 -21
- package/templates/native-rust-delegate/backend/src/delegate.rs +171 -0
- package/templates/native-rust-jobs/backend/src/jobs.rs +240 -0
- package/templates/native-rust-root-ops/backend/src/root_ops.rs +83 -0
- package/templates/native-rust-vue/README.md +35 -34
- package/templates/native-rust/backend/THIRD_PARTY_NOTICES.txt +0 -207
- package/templates/native-rust/backend/src/app/background/mod.rs +0 -424
- package/templates/native-rust/backend/src/app/background/owner.rs +0 -77
- package/templates/native-rust/backend/src/app/background/owner_tests.rs +0 -174
- package/templates/native-rust/backend/src/app/background/schedule.rs +0 -126
- package/templates/native-rust/backend/src/app/background/schedule_tests.rs +0 -108
- package/templates/native-rust/backend/src/app/background/tests.rs +0 -505
- package/templates/native-rust/backend/src/app/background/work.rs +0 -78
- package/templates/native-rust/backend/src/app/background/work_tests.rs +0 -123
- package/templates/native-rust/backend/src/app/background_routes.rs +0 -22
- package/templates/native-rust/backend/src/app/delegate_config.rs +0 -188
- package/templates/native-rust/backend/src/app/delegate_routes.rs +0 -22
- package/templates/native-rust/backend/src/app/identity.rs +0 -56
- package/templates/native-rust/backend/src/app/migrations.rs +0 -79
- package/templates/native-rust/backend/src/app/mod.rs +0 -30
- package/templates/native-rust/backend/src/app/portal.rs +0 -317
- package/templates/native-rust/backend/src/app/root_routes.rs +0 -23
- package/templates/native-rust/backend/src/app/routes.rs +0 -315
- package/templates/native-rust/backend/src/app/session_files.rs +0 -19
- package/templates/native-rust/backend/src/app/shared_folder.rs +0 -383
- package/templates/native-rust/backend/src/app/test_support.rs +0 -202
- package/templates/native-rust/backend/src/app/uninstall.rs +0 -38
- package/templates/native-rust/backend/src/bin/api.rs +0 -72
- package/templates/native-rust/backend/src/bin/background-worker.rs +0 -37
- package/templates/native-rust/backend/src/build_env.rs +0 -155
- package/templates/native-rust/backend/src/lib.rs +0 -6
- package/templates/native-rust/backend/src/permissions.rs +0 -54
- package/templates/native-rust/backend/src/process.rs +0 -91
- package/templates/native-rust/i18n/glossary.json +0 -663
- package/templates/native-rust/i18n/messages/de.json +0 -30
- package/templates/native-rust/i18n/messages/en.json +0 -30
- package/templates/native-rust/i18n/messages/es.json +0 -30
- package/templates/native-rust/i18n/messages/fr.json +0 -30
- package/templates/native-rust/i18n/messages/it.json +0 -30
- package/templates/native-rust/i18n/messages/ja.json +0 -30
- package/templates/native-rust/i18n/messages/ko.json +0 -30
- package/templates/native-rust/i18n/messages/zh-CN.json +0 -30
- package/templates/native-rust/i18n/messages/zh-TW.json +0 -30
- package/templates/native-rust/ui/app.css +0 -45
- package/templates/native-rust/ui/app.js +0 -111
- package/templates/native-rust-delegate/backend/src/app/delegate_routes.rs +0 -92
- package/templates/native-rust-delegate/backend/src/bin/background-worker.rs +0 -52
- package/templates/native-rust-jobs/backend/src/app/background_routes.rs +0 -109
- package/templates/native-rust-root-ops/backend/src/app/root_routes.rs +0 -475
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import stat
|
|
3
|
+
import tempfile
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from support import Error, call
|
|
7
|
+
|
|
8
|
+
import routes
|
|
9
|
+
|
|
10
|
+
SAVED = {"state": "queued"}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DelegateTest(unittest.TestCase):
|
|
14
|
+
def setUp(self):
|
|
15
|
+
self.dir = tempfile.TemporaryDirectory()
|
|
16
|
+
self.addCleanup(self.dir.cleanup)
|
|
17
|
+
self.staging = os.path.join(self.dir.name, "executor-staging")
|
|
18
|
+
self.app = routes.build(routes.Config("ExampleApp", self.dir.name))
|
|
19
|
+
|
|
20
|
+
def save(self, job_id="job-1", user="alice", move=lambda p: SAVED):
|
|
21
|
+
params = {"delegation_id": "d1", "job_id": job_id}
|
|
22
|
+
return call(self.app, "report.save", params, user=user, admin=False, services={"delegate.move_from_staging": move})
|
|
23
|
+
|
|
24
|
+
def test_save_moves_a_private_report_to_the_callers_destination(self):
|
|
25
|
+
reply = self.save()
|
|
26
|
+
self.assertEqual(reply.data, SAVED)
|
|
27
|
+
path = os.path.join(self.staging, "report-job-1.txt")
|
|
28
|
+
with open(path, encoding="utf-8") as f:
|
|
29
|
+
self.assertIn("alice", f.read())
|
|
30
|
+
self.assertEqual(stat.S_IMODE(os.stat(path).st_mode), 0o600)
|
|
31
|
+
move = {"delegation_id": "d1", "job_id": "job-1", "staging": "report-job-1.txt", "name": "report-job-1.txt", "size": os.path.getsize(path)}
|
|
32
|
+
self.assertEqual(reply.services, [("delegate.move_from_staging", move)])
|
|
33
|
+
|
|
34
|
+
def test_a_repeated_save_sends_the_same_file_again(self):
|
|
35
|
+
first = self.save().services
|
|
36
|
+
path = os.path.join(self.staging, "report-job-1.txt")
|
|
37
|
+
with open(path, encoding="utf-8") as f:
|
|
38
|
+
report = f.read()
|
|
39
|
+
self.assertEqual(self.save(user="bob").services, first)
|
|
40
|
+
with open(path, encoding="utf-8") as f:
|
|
41
|
+
self.assertEqual(f.read(), report)
|
|
42
|
+
|
|
43
|
+
def test_save_passes_on_the_executors_refusal(self):
|
|
44
|
+
def denied(params):
|
|
45
|
+
raise Error("DELEGATION_DENIED", "The destination is no longer allowed.")
|
|
46
|
+
|
|
47
|
+
self.assertEqual(self.save(move=denied).error["code"], "DELEGATION_DENIED")
|
|
48
|
+
|
|
49
|
+
def test_save_refuses_invalid_params_before_writing(self):
|
|
50
|
+
for params in [None, {"delegation_id": "d1"}, {"delegation_id": "d1", "job_id": "../x"}, {"delegation_id": "d1", "job_id": ""}, {"delegation_id": 1, "job_id": "j"}]:
|
|
51
|
+
with self.subTest(params=params):
|
|
52
|
+
reply = call(self.app, "report.save", params)
|
|
53
|
+
self.assertEqual((reply.error["code"], reply.services), ("bad_request", []))
|
|
54
|
+
self.assertFalse(os.path.exists(self.staging))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
unittest.main()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Background jobs: hash a NAS file the caller may read.
|
|
2
|
+
|
|
3
|
+
The SDK's worker runs each job as the package account: it hashes the job's
|
|
4
|
+
input (SHA-256) and keeps the result for the job's owner, the caller. The
|
|
5
|
+
runtime serves files.read with the caller's DSM session and bg.* for the
|
|
6
|
+
caller's own jobs only.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import shutil
|
|
11
|
+
import tempfile
|
|
12
|
+
|
|
13
|
+
from synopkgland_app import Error
|
|
14
|
+
|
|
15
|
+
from params import fields
|
|
16
|
+
|
|
17
|
+
# A job's input is at most 512 KiB, copied into the job when it is submitted.
|
|
18
|
+
MAX_INPUT = 512 * 1024
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def register(app, cfg):
|
|
22
|
+
app.method("hash.submit")(lambda ctx, params: submit(cfg, ctx, params))
|
|
23
|
+
app.method("hash.get")(lambda ctx, params: ctx.runtime("bg.get", fields(params, id=str)))
|
|
24
|
+
app.method("hash.cancel")(lambda ctx, params: ctx.runtime("bg.cancel", fields(params, id=str)))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def submit(cfg, ctx, params):
|
|
28
|
+
p = fields(params, key=str, path=str)
|
|
29
|
+
read = ctx.runtime("files.read", {"path": p["path"]})
|
|
30
|
+
if read["size"] > MAX_INPUT:
|
|
31
|
+
raise Error("limit_exceeded", "The file is larger than allowed.")
|
|
32
|
+
# bg.submit takes its input from the data directory; the call's spool is elsewhere.
|
|
33
|
+
inputs = os.path.join(cfg.data_dir, "hash-inputs")
|
|
34
|
+
os.makedirs(inputs, mode=0o700, exist_ok=True)
|
|
35
|
+
with open(read["path"], "rb") as source, tempfile.NamedTemporaryFile(dir=inputs, delete=False) as copy:
|
|
36
|
+
shutil.copyfileobj(source, copy)
|
|
37
|
+
try:
|
|
38
|
+
return ctx.runtime("bg.submit", {"key": p["key"], "name": os.path.basename(p["path"]), "input": copy.name})
|
|
39
|
+
finally:
|
|
40
|
+
os.unlink(copy.name)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
from support import Error, call
|
|
6
|
+
|
|
7
|
+
import routes
|
|
8
|
+
|
|
9
|
+
JOB = {"job": {"id": "j1", "state": "queued"}, "duplicate": False}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class JobsTest(unittest.TestCase):
|
|
13
|
+
def setUp(self):
|
|
14
|
+
self.dir = tempfile.TemporaryDirectory()
|
|
15
|
+
self.addCleanup(self.dir.cleanup)
|
|
16
|
+
self.data = os.path.join(self.dir.name, "var")
|
|
17
|
+
os.mkdir(self.data)
|
|
18
|
+
self.app = routes.build(routes.Config("ExampleApp", self.data))
|
|
19
|
+
self.submitted = []
|
|
20
|
+
|
|
21
|
+
def spool(self, content):
|
|
22
|
+
path = os.path.join(self.dir.name, "spool")
|
|
23
|
+
with open(path, "wb") as f:
|
|
24
|
+
f.write(content)
|
|
25
|
+
return {"path": path, "size": len(content)}
|
|
26
|
+
|
|
27
|
+
def submit(self, params):
|
|
28
|
+
# The runtime reads the input file while it answers bg.submit.
|
|
29
|
+
with open(params["input"], "rb") as f:
|
|
30
|
+
self.submitted.append(dict(params, content=f.read()))
|
|
31
|
+
return JOB
|
|
32
|
+
|
|
33
|
+
def test_submit_hashes_a_copy_of_the_callers_file(self):
|
|
34
|
+
services = {"files.read": lambda p: self.spool(b"hello"), "bg.submit": self.submit}
|
|
35
|
+
reply = call(self.app, "hash.submit", {"key": "k1", "path": "/home/report.txt"}, services=services)
|
|
36
|
+
self.assertEqual(reply.data, JOB)
|
|
37
|
+
self.assertEqual([op for op, _ in reply.services], ["files.read", "bg.submit"])
|
|
38
|
+
self.assertEqual(reply.services[0][1], {"path": "/home/report.txt"})
|
|
39
|
+
[submitted] = self.submitted
|
|
40
|
+
self.assertEqual((submitted["key"], submitted["name"], submitted["content"]), ("k1", "report.txt", b"hello"))
|
|
41
|
+
self.assertTrue(submitted["input"].startswith(self.data + os.sep))
|
|
42
|
+
self.assertFalse(os.path.exists(submitted["input"]), "the copy is removed once submitted")
|
|
43
|
+
|
|
44
|
+
def test_submit_refuses_a_file_over_the_job_input_limit(self):
|
|
45
|
+
services = {"files.read": lambda p: self.spool(b"x" * (512 * 1024 + 1)), "bg.submit": self.submit}
|
|
46
|
+
reply = call(self.app, "hash.submit", {"key": "k1", "path": "/home/big.bin"}, services=services)
|
|
47
|
+
self.assertEqual(reply.error["code"], "limit_exceeded")
|
|
48
|
+
self.assertEqual(self.submitted, [])
|
|
49
|
+
|
|
50
|
+
def test_submit_passes_on_the_runtimes_refusal(self):
|
|
51
|
+
def unreadable(params):
|
|
52
|
+
raise Error("read_failed", "Unable to read the file.")
|
|
53
|
+
|
|
54
|
+
reply = call(self.app, "hash.submit", {"key": "k1", "path": "/home/a"}, services={"files.read": unreadable})
|
|
55
|
+
self.assertEqual(reply.error["code"], "read_failed")
|
|
56
|
+
|
|
57
|
+
def test_submit_refuses_invalid_params_before_any_service(self):
|
|
58
|
+
for params in [None, {"key": "k1"}, {"key": 1, "path": "/home/a"}, {"key": "k1", "path": "/home/a", "x": 1}]:
|
|
59
|
+
with self.subTest(params=params):
|
|
60
|
+
reply = call(self.app, "hash.submit", params)
|
|
61
|
+
self.assertEqual((reply.error["code"], reply.services), ("bad_request", []))
|
|
62
|
+
|
|
63
|
+
def test_get_and_cancel_act_on_the_callers_job(self):
|
|
64
|
+
for method, op in [("hash.get", "bg.get"), ("hash.cancel", "bg.cancel")]:
|
|
65
|
+
with self.subTest(method=method):
|
|
66
|
+
reply = call(self.app, method, {"id": "j1"}, services={op: lambda p: JOB["job"]})
|
|
67
|
+
self.assertEqual((reply.data, reply.services), (JOB["job"], [(op, {"id": "j1"})]))
|
|
68
|
+
self.assertEqual(call(self.app, method, {}).error["code"], "bad_request")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
unittest.main()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Root operations: run root/whoami.sh as root.
|
|
2
|
+
|
|
3
|
+
The executor runs only the root programs an administrator approved, and
|
|
4
|
+
checks the caller's DSM session and Application Privileges itself; the App
|
|
5
|
+
names the program and never runs anything as root.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from synopkgland_app import Error
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def register(app, cfg):
|
|
12
|
+
app.method("whoami.run")(lambda ctx, params: whoami(ctx, params))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def whoami(ctx, params):
|
|
16
|
+
if params not in (None, {}):
|
|
17
|
+
raise Error.bad_request("whoami.run takes no params")
|
|
18
|
+
return ctx.runtime("root.run", {"name": "whoami"})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import tempfile
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
from support import Error, call
|
|
5
|
+
|
|
6
|
+
import routes
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RootOpsTest(unittest.TestCase):
|
|
10
|
+
def setUp(self):
|
|
11
|
+
self.dir = tempfile.TemporaryDirectory()
|
|
12
|
+
self.addCleanup(self.dir.cleanup)
|
|
13
|
+
self.app = routes.build(routes.Config("ExampleApp", self.dir.name))
|
|
14
|
+
|
|
15
|
+
def test_whoami_runs_the_approved_root_program(self):
|
|
16
|
+
output = {"exit_code": 0, "stdout": "0\n", "stderr": ""}
|
|
17
|
+
for params in [None, {}]:
|
|
18
|
+
with self.subTest(params=params):
|
|
19
|
+
reply = call(self.app, "whoami.run", params, services={"root.run": lambda p: output})
|
|
20
|
+
self.assertEqual((reply.data, reply.services), (output, [("root.run", {"name": "whoami"})]))
|
|
21
|
+
|
|
22
|
+
def test_whoami_passes_on_the_executors_refusal(self):
|
|
23
|
+
def paused(params):
|
|
24
|
+
raise Error("EXECUTOR_PAUSED", "The executor is paused.")
|
|
25
|
+
|
|
26
|
+
reply = call(self.app, "whoami.run", {}, services={"root.run": paused})
|
|
27
|
+
self.assertEqual(reply.error["code"], "EXECUTOR_PAUSED")
|
|
28
|
+
|
|
29
|
+
def test_whoami_takes_no_params(self):
|
|
30
|
+
self.assertEqual(call(self.app, "whoami.run", {"name": "other"}).error["code"], "bad_request")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
unittest.main()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Native Python with Vue
|
|
2
|
+
|
|
3
|
+
The Vue shell is the same as the Go template's. Edit `frontend/src/App.vue`
|
|
4
|
+
and `main.ts` to build a DSM-window app with the public `@synopackageland/ui`
|
|
5
|
+
components and embedded theme. The shell, sign-in, files, portal, jobs and
|
|
6
|
+
delegate components come from `@synopackageland/sdk/vue` (types from
|
|
7
|
+
`@synopackageland/sdk`); its version must equal the `backend.sdk` in dsm-package.yaml. See
|
|
8
|
+
<https://github.com/synopackageland/syno-pkg-land-sdk> for the components and their props.
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
cd frontend
|
|
12
|
+
npm ci
|
|
13
|
+
npm test
|
|
14
|
+
npm run build
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The base app reads package status and edits the package greeting through
|
|
18
|
+
`ctx.call`. Errors show the app's own `frontend/src/i18n` text, or the SDK's
|
|
19
|
+
`bridgeErrorCopy` for the codes the bridge runtime adds (`app_unavailable`
|
|
20
|
+
and the services' refusals). Each window has its own Vue mount, request state
|
|
21
|
+
and host subscriptions; unmount disposes subscriptions and ignores late responses.
|
|
22
|
+
|
|
23
|
+
The `--with jobs`, `root-ops` and `delegate` examples add their methods to
|
|
24
|
+
the Python App; the shell's panels call them with `ctx.call`. The SDK's
|
|
25
|
+
background-jobs page and executor settings call the runtime's own browser
|
|
26
|
+
methods (`bg.*`, `executor.*`); check the SDK release notes for the ones a
|
|
27
|
+
bridge App's runtime serves.
|
|
28
|
+
|
|
29
|
+
Use the installed `@synopackageland/ui`; package.json pins its version.
|
|
30
|
+
`App.vue` lays the shell out with the SDK's `AppShell` (`@synopackageland/sdk/vue`):
|
|
31
|
+
`pages` from `shellPages`, the header buttons in `#actions` and the pages in its
|
|
32
|
+
default slot. Given `ctx`, it follows the launch. In a DSM desktop window it
|
|
33
|
+
keeps `wide-layout="window"`, even maximized; the DSM host sets the 700px
|
|
34
|
+
minimum window width. A Portal or standalone launch owns the whole page
|
|
35
|
+
(`ctx.fullPage`, from the generated window): `AppShell` takes
|
|
36
|
+
`wide-layout="web"` (the SaaS layout at 1200px or wider, mobile on a phone) and
|
|
37
|
+
a device-width viewport, undone at unmount, since DSM's page has no
|
|
38
|
+
device-width viewport. Its full-page sidebar names the App with
|
|
39
|
+
`ctx.displayName` and `ctx.appIcon` (without them the page title and icon) and
|
|
40
|
+
shows the signed-in DSM account and its role from `ctx.user`. See the ui's
|
|
41
|
+
`docs/adaptive-layout.md`.
|
|
42
|
+
`AppShell`'s `app-root` wraps it in an app-local `AppRoot` (app modal scope, on
|
|
43
|
+
a full page as well, bounded at `height: 100%`) with the UI locale from
|
|
44
|
+
`locale`; do not add another `AppRoot`.
|
|
45
|
+
In a DSM window (not a full page) that `AppRoot` takes
|
|
46
|
+
`dialog-bounds="viewport"`, so Dialogs can be dragged out of the window, and
|
|
47
|
+
pressing one calls `ctx.host.activateWindow()` to bring the window to the
|
|
48
|
+
front. Elsewhere Dialogs stay inside the App. A confirmed sign-in need sets
|
|
49
|
+
`denied`: the `#denied` slot's `LoginPage` takes the place of the App's pages.
|
|
50
|
+
Import `themes/dsm7-embedded.css` and assign each mount a unique
|
|
51
|
+
`app.config.idPrefix` before mounting.
|
|
52
|
+
|
|
53
|
+
Start with AGENTS.md, `synopkgland skill list` and `synopkgland skill path`.
|
|
54
|
+
Read `synopkgland skill show ui-vue` for direct Vue component work. For UI
|
|
55
|
+
discovery, run `npx --no syno-pkg-land-ui catalog --json` and
|
|
56
|
+
`npx --no syno-pkg-land-ui describe Button --json` from the installed frontend.
|