@synopackageland/cli 0.6.0 → 0.6.1
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 +12 -1
- package/dist/authorization-sdk.d.ts +7 -0
- package/dist/authorization-sdk.js +7 -0
- package/dist/cli.js +16 -12
- package/dist/error-registry.js +5 -0
- package/dist/standalone/inspect-spk.d.ts +1 -1
- package/dist/standalone/inspect-spk.js +14 -0
- package/dist/standalone/native-authorization-window.d.ts +2 -0
- package/dist/standalone/native-authorization-window.js +28 -0
- package/dist/standalone/native-layout.d.ts +79 -0
- package/dist/standalone/native-layout.js +345 -0
- package/dist/standalone/native-root-window.d.ts +26 -0
- package/dist/standalone/native-root-window.js +282 -0
- package/dist/templates/native-authorization/AuthorizationGate.vue +89 -0
- package/dist/templates/native-authorization/i18n/de.json +36 -0
- package/dist/templates/native-authorization/i18n/en.json +36 -0
- package/dist/templates/native-authorization/i18n/es.json +36 -0
- package/dist/templates/native-authorization/i18n/fr.json +36 -0
- package/dist/templates/native-authorization/i18n/index.ts +15 -0
- package/dist/templates/native-authorization/i18n/it.json +36 -0
- package/dist/templates/native-authorization/i18n/ja.json +36 -0
- package/dist/templates/native-authorization/i18n/ko.json +36 -0
- package/dist/templates/native-authorization/i18n/zh-CN.json +36 -0
- package/dist/templates/native-authorization/i18n/zh-TW.json +36 -0
- package/dist/validate.js +2 -1
- package/package.json +8 -2
- package/skills/SKILL.md +6 -2
- package/templates/native-authorization/AuthorizationGate.vue +89 -0
- package/templates/native-authorization/i18n/de.json +36 -0
- package/templates/native-authorization/i18n/en.json +36 -0
- package/templates/native-authorization/i18n/es.json +36 -0
- package/templates/native-authorization/i18n/fr.json +36 -0
- package/templates/native-authorization/i18n/index.ts +15 -0
- package/templates/native-authorization/i18n/it.json +36 -0
- package/templates/native-authorization/i18n/ja.json +36 -0
- package/templates/native-authorization/i18n/ko.json +36 -0
- package/templates/native-authorization/i18n/zh-CN.json +36 -0
- package/templates/native-authorization/i18n/zh-TW.json +36 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Build-time adapter for explicitly approved native product tasks. */
|
|
2
|
+
/** What the window embeds per root operation: the task it may create, and how to recognize it. */
|
|
3
|
+
export interface RootTask {
|
|
4
|
+
op: string;
|
|
5
|
+
name: string;
|
|
6
|
+
header: string;
|
|
7
|
+
script: string;
|
|
8
|
+
/** Whether the package backend serves `root.output` for this op; helper ops report through the helper instead. */
|
|
9
|
+
output: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* `createRootAccess(options)` → `{ state, authorize, run, remove }`, plain
|
|
13
|
+
* ES5 for DSM's desktop. Every DSM call goes through `options.send` (the
|
|
14
|
+
* AppWindow's sendWebAPI) as the signed-in administrator:
|
|
15
|
+
*
|
|
16
|
+
* - authorize: DSM's own password confirmation, then one disabled root task
|
|
17
|
+
* with exactly the embedded script. The one-use token goes into that single
|
|
18
|
+
* create call and is never kept.
|
|
19
|
+
* - run: re-reads the task and runs it only if it is still exactly ours, then
|
|
20
|
+
* waits for its history entry and asks the backend for the output. Repair
|
|
21
|
+
* additionally requires fresh PasswordConfirm and another exact task check.
|
|
22
|
+
* - an older version of our task is "outdated" and never runs; authorizing
|
|
23
|
+
* again replaces it. A same-named task that is not ours is a "conflict" and
|
|
24
|
+
* is never run, replaced or deleted.
|
|
25
|
+
*/
|
|
26
|
+
export declare const ROOT_RUNTIME = "function createRootAccess(options) {\n var API = \"SYNO.Core.TaskScheduler\";\n var POLL_MS = 500, POLLS = 60;\n var byOp = {}, running = {};\n var legacy = null, probe = null;\n options.tasks.forEach(function (task) { byOp[task.op] = task; });\n\n function fail(code) {\n var error = new Error(code);\n error.code = code;\n return error;\n }\n function begin(op) {\n if (!Object.prototype.hasOwnProperty.call(byOp, op)) return Promise.reject(fail(\"root_unknown_op\"));\n if (options.user().admin !== true) return Promise.reject(fail(\"admin_required\"));\n return Promise.resolve(byOp[op]);\n }\n function readOutput(task) {\n return task.output ? options.call(\"root.output\", { op: task.op }) : Promise.resolve(null);\n }\n function isRootTask(summary) {\n return summary.owner === \"root\" && summary.real_owner === \"root\" && summary.enable === false && summary.type === \"script\";\n }\n function listTasks() {\n if (legacy === false) return options.send(API, \"list\", 3, { start: 0, limit: 500 });\n if (legacy === true) return legacyList();\n if (!probe) {\n probe = options.send(API, \"list\", 3, { start: 0, limit: 500 }).then(function (data) {\n legacy = false;\n return data;\n }, function (error) {\n if (!error || (error.code !== \"dsm_103\" && error.code !== 103)) {\n probe = null;\n throw error;\n }\n legacy = true;\n return legacyList();\n });\n }\n return probe;\n }\n function legacyList() {\n return options.send(API, \"list\", 2, { offset: 0, limit: 500 }).then(function (data) {\n // Refuse a truncated snapshot: a same-named task could lie outside it.\n if (!data || !Array.isArray(data.tasks) || data.tasks.length > 500\n || !(typeof data.total === \"number\" && isFinite(data.total) && Math.floor(data.total) === data.total) || data.total !== data.tasks.length) throw fail(\"root_bad_response\");\n return data;\n });\n }\n function readTask(ref) {\n return options.send(API, \"get\", legacy ? 3 : 4, ref);\n }\n function createTask(params) {\n return options.send(legacy ? API : API + \".Root\", \"create\", legacy ? 3 : 4, params);\n }\n function deleteTask(ref) {\n return options.send(API, \"delete\", 2, { tasks: [ref] });\n }\n function runTask(ref) {\n return options.send(API, \"run\", 2, { tasks: [ref] });\n }\n function find(task) {\n return listTasks().then(function (data) {\n if (!data || !Array.isArray(data.tasks)) throw fail(\"root_bad_response\");\n var named = data.tasks.filter(function (summary) { return summary && summary.name === task.name; });\n if (named.length === 0) return { state: \"missing\" };\n if (named.length > 1 || !isRootTask(named[0])) return { state: \"conflict\" };\n var ref = { id: named[0].id, real_owner: named[0].real_owner };\n return readTask(ref).then(function (detail) {\n var script = detail && detail.extra && detail.extra.script;\n if (!detail || String(detail.id) !== String(ref.id) || detail.name !== task.name\n || detail.owner !== \"root\" || detail.real_owner !== ref.real_owner || detail.enable !== false || detail.type !== \"script\" || typeof script !== \"string\") {\n return { state: \"conflict\" };\n }\n if (script === task.script) return { state: \"ready\", ref: ref };\n if (script.indexOf(task.header) === 0) return { state: \"outdated\", ref: ref };\n return { state: \"conflict\" };\n });\n });\n }\n function definition(task, user) {\n var hours = [];\n for (var h = 1; h <= 23; h++) hours.push(h);\n return {\n name: task.name,\n real_owner: user,\n owner: \"root\",\n enable: false,\n type: \"script\",\n schedule: {\n date_type: 0, week_day: \"0,1,2,3,4,5,6\", repeat_date: 1001, monthly_week: [], hour: 0, minute: 0,\n repeat_hour: 0, repeat_min: 0, last_work_hour: 0,\n repeat_min_store_config: [1, 5, 10, 15, 20, 30], repeat_hour_store_config: hours\n },\n extra: { notify_enable: false, script: task.script, notify_mail: \"\", notify_if_error: false }\n };\n }\n function newest(history) {\n if (!Array.isArray(history)) throw fail(\"root_bad_response\");\n return history.reduce(function (latest, entry) {\n return entry && (!latest || Number(entry.timestamp) > Number(latest.timestamp)) ? entry : latest;\n }, null);\n }\n function history(ref) {\n return options.send(API, \"get_history_status_list\", 1, { id: ref.id }).then(newest);\n }\n function waitForRun(ref, before, polls) {\n return history(ref).then(function (latest) {\n var finished = latest && (!before || latest.timestamp !== before.timestamp)\n && latest.exit_type !== \"running\" && latest.stop_time;\n if (finished) {\n if (latest.exit_code !== 0) throw fail(\"root_task_failed\");\n return;\n }\n if (polls <= 1) throw fail(\"root_timeout\");\n return options.wait(POLL_MS).then(function () { return waitForRun(ref, before, polls - 1); });\n });\n }\n\n function validTime(value) {\n if (typeof value !== \"string\" || !/^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$/.test(value)) return false;\n var normalized = value.replace(\" \", \"T\") + \"Z\";\n var date = new Date(normalized);\n return !isNaN(date.getTime()) && date.toISOString().slice(0, 19) === normalized.slice(0, 19);\n }\n function bounded(call, deadline) {\n // A poll count alone cannot bound a stalled DSM callback. Discard late replies.\n return new Promise(function (resolve, reject) {\n var remaining = deadline - Date.now();\n if (remaining <= 0) { reject(fail(\"root_timeout\")); return; }\n var timer = setTimeout(function () { reject(fail(\"root_timeout\")); }, remaining);\n Promise.resolve().then(call).then(function (value) {\n clearTimeout(timer);\n if (Date.now() >= deadline) reject(fail(\"root_timeout\"));\n else resolve(value);\n }, function (error) { clearTimeout(timer); reject(error); });\n });\n }\n function legacyHistory(ref, deadline) {\n return bounded(function () { return options.send(API, \"view\", 1, { id: ref.id }); }, deadline).then(function (records) {\n // DSM 7.1 omits data entirely when a task has never run.\n if (records === undefined) records = [];\n if (!Array.isArray(records) || records.length > 500) throw fail(\"root_bad_response\");\n var seen = {};\n records.forEach(function (record) {\n if (!record || !validTime(record.start_time) || typeof record.stop_time !== \"string\"\n || (record.stop_time && (!validTime(record.stop_time) || record.stop_time < record.start_time))\n || typeof record.exit_type !== \"string\" || !(typeof record.exit_code === \"number\" && isFinite(record.exit_code) && Math.floor(record.exit_code) === record.exit_code)\n || seen[record.start_time]) throw fail(\"root_bad_response\");\n seen[record.start_time] = true;\n });\n return records;\n });\n }\n function waitForLegacyRun(ref, before, deadline, polls) {\n return legacyHistory(ref, deadline).then(function (records) {\n var previous = before.map(function (record) { return record.start_time; });\n var newestBefore = previous.slice().sort().pop();\n var fresh = records.filter(function (record) { return previous.indexOf(record.start_time) === -1; });\n if (fresh.length > 1) throw fail(\"root_bad_response\");\n var latest = fresh[0];\n if (latest && (!newestBefore || latest.start_time > newestBefore) && latest.stop_time) {\n if (latest.exit_code !== 0 || latest.exit_type !== \"normal\") throw fail(\"root_task_failed\");\n return;\n }\n if (polls <= 1 || Date.now() >= deadline) throw fail(\"root_timeout\");\n return options.wait(Math.min(POLL_MS, deadline - Date.now())).then(function () {\n return waitForLegacyRun(ref, before, deadline, polls - 1);\n });\n });\n }\n function execute(task, verified) {\n if (!legacy) {\n return history(verified.ref).then(function (before) {\n return runTask(verified.ref).then(function () { return waitForRun(verified.ref, before, POLLS); });\n });\n }\n var deadline = Date.now() + POLL_MS * POLLS;\n return legacyHistory(verified.ref, deadline).then(function (before) {\n if (before.some(function (record) { return !record.stop_time || record.exit_type === \"running\"; })) throw fail(\"root_not_ready\");\n // Mutating calls are sent exactly once, never retried with another profile.\n return bounded(function () { return runTask(verified.ref); }, deadline).then(function () {\n return waitForLegacyRun(verified.ref, before, deadline, POLLS);\n });\n }).then(function () {\n return bounded(function () { return find(task); }, deadline).then(function (after) {\n if (after.state !== \"ready\" || String(after.ref.id) !== String(verified.ref.id)) throw fail(\"root_verify_failed\");\n });\n });\n }\n function performRun(op) {\n return begin(op).then(function (task) {\n return find(task).then(function (found) {\n if (found.state !== \"ready\") throw fail(\"root_not_ready\");\n var consent = op === \"helper_repair\" ? options.confirmPassword() : Promise.resolve({ confirmed: true });\n return consent.then(function (confirmation) {\n if (!confirmation || confirmation.confirmed !== true) throw fail(\"root_cancelled\");\n return op === \"helper_repair\" ? begin(op).then(find) : found;\n }).then(function (verified) {\n if (verified.state !== \"ready\") throw fail(\"root_not_ready\");\n return execute(task, verified);\n });\n }).then(function () { return readOutput(task); });\n });\n }\n\n return {\n state: function (op) {\n return begin(op).then(find).then(function (found) { return found.state; });\n },\n authorize: function (op) {\n return begin(op).then(function (task) {\n return find(task).then(function (found) {\n if (found.state === \"ready\") return \"ready\";\n if (found.state === \"conflict\") throw fail(\"root_conflict\");\n return options.confirmPassword().then(function (confirmation) {\n if (!confirmation || confirmation.confirmed !== true) return \"cancelled\";\n if (!legacy && (typeof confirmation.token !== \"string\" || !confirmation.token)) throw fail(\"root_verify_failed\");\n var params = definition(task, options.user().name);\n if (!legacy) params.SynoConfirmPWToken = confirmation.token;\n // Re-read after the legacy client-side dialog before replacing a task.\n var checked = legacy ? begin(op).then(find) : Promise.resolve(found);\n return checked.then(function (current) {\n if (current.state === \"conflict\") throw fail(\"root_conflict\");\n if (current.state === \"ready\") return;\n var replaced = current.state === \"outdated\" ? deleteTask(current.ref) : Promise.resolve();\n return replaced.then(function () { return createTask(params); });\n }).then(function () {\n return find(task);\n }).then(function (after) {\n if (after.state !== \"ready\") throw fail(\"root_verify_failed\");\n return \"ready\";\n });\n });\n });\n });\n },\n run: function (op) {\n if (!Object.prototype.hasOwnProperty.call(byOp, op)) return Promise.reject(fail(\"root_unknown_op\"));\n if (options.user().admin !== true) return Promise.reject(fail(\"admin_required\"));\n if (running[op]) return running[op];\n var pending = performRun(op).then(function (value) {\n delete running[op];\n return value;\n }, function (error) { delete running[op]; throw error; });\n running[op] = pending;\n return pending;\n },\n remove: function (op) {\n return begin(op).then(function (task) {\n return find(task).then(function (found) {\n if (found.state === \"missing\") return \"missing\";\n if (found.state === \"conflict\") throw fail(\"root_conflict\");\n return deleteTask(found.ref).then(function () {\n return find(task);\n }).then(function (after) {\n if (after.state !== \"missing\") throw fail(\"root_verify_failed\");\n return \"missing\";\n });\n });\n });\n },\n output: function (op) {\n return begin(op).then(readOutput);\n }\n };\n}";
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `createRootAccess(options)` → `{ state, authorize, run, remove }`, plain
|
|
3
|
+
* ES5 for DSM's desktop. Every DSM call goes through `options.send` (the
|
|
4
|
+
* AppWindow's sendWebAPI) as the signed-in administrator:
|
|
5
|
+
*
|
|
6
|
+
* - authorize: DSM's own password confirmation, then one disabled root task
|
|
7
|
+
* with exactly the embedded script. The one-use token goes into that single
|
|
8
|
+
* create call and is never kept.
|
|
9
|
+
* - run: re-reads the task and runs it only if it is still exactly ours, then
|
|
10
|
+
* waits for its history entry and asks the backend for the output. Repair
|
|
11
|
+
* additionally requires fresh PasswordConfirm and another exact task check.
|
|
12
|
+
* - an older version of our task is "outdated" and never runs; authorizing
|
|
13
|
+
* again replaces it. A same-named task that is not ours is a "conflict" and
|
|
14
|
+
* is never run, replaced or deleted.
|
|
15
|
+
*/
|
|
16
|
+
export const ROOT_RUNTIME = `function createRootAccess(options) {
|
|
17
|
+
var API = "SYNO.Core.TaskScheduler";
|
|
18
|
+
var POLL_MS = 500, POLLS = 60;
|
|
19
|
+
var byOp = {}, running = {};
|
|
20
|
+
var legacy = null, probe = null;
|
|
21
|
+
options.tasks.forEach(function (task) { byOp[task.op] = task; });
|
|
22
|
+
|
|
23
|
+
function fail(code) {
|
|
24
|
+
var error = new Error(code);
|
|
25
|
+
error.code = code;
|
|
26
|
+
return error;
|
|
27
|
+
}
|
|
28
|
+
function begin(op) {
|
|
29
|
+
if (!Object.prototype.hasOwnProperty.call(byOp, op)) return Promise.reject(fail("root_unknown_op"));
|
|
30
|
+
if (options.user().admin !== true) return Promise.reject(fail("admin_required"));
|
|
31
|
+
return Promise.resolve(byOp[op]);
|
|
32
|
+
}
|
|
33
|
+
function readOutput(task) {
|
|
34
|
+
return task.output ? options.call("root.output", { op: task.op }) : Promise.resolve(null);
|
|
35
|
+
}
|
|
36
|
+
function isRootTask(summary) {
|
|
37
|
+
return summary.owner === "root" && summary.real_owner === "root" && summary.enable === false && summary.type === "script";
|
|
38
|
+
}
|
|
39
|
+
function listTasks() {
|
|
40
|
+
if (legacy === false) return options.send(API, "list", 3, { start: 0, limit: 500 });
|
|
41
|
+
if (legacy === true) return legacyList();
|
|
42
|
+
if (!probe) {
|
|
43
|
+
probe = options.send(API, "list", 3, { start: 0, limit: 500 }).then(function (data) {
|
|
44
|
+
legacy = false;
|
|
45
|
+
return data;
|
|
46
|
+
}, function (error) {
|
|
47
|
+
if (!error || (error.code !== "dsm_103" && error.code !== 103)) {
|
|
48
|
+
probe = null;
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
legacy = true;
|
|
52
|
+
return legacyList();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return probe;
|
|
56
|
+
}
|
|
57
|
+
function legacyList() {
|
|
58
|
+
return options.send(API, "list", 2, { offset: 0, limit: 500 }).then(function (data) {
|
|
59
|
+
// Refuse a truncated snapshot: a same-named task could lie outside it.
|
|
60
|
+
if (!data || !Array.isArray(data.tasks) || data.tasks.length > 500
|
|
61
|
+
|| !(typeof data.total === "number" && isFinite(data.total) && Math.floor(data.total) === data.total) || data.total !== data.tasks.length) throw fail("root_bad_response");
|
|
62
|
+
return data;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function readTask(ref) {
|
|
66
|
+
return options.send(API, "get", legacy ? 3 : 4, ref);
|
|
67
|
+
}
|
|
68
|
+
function createTask(params) {
|
|
69
|
+
return options.send(legacy ? API : API + ".Root", "create", legacy ? 3 : 4, params);
|
|
70
|
+
}
|
|
71
|
+
function deleteTask(ref) {
|
|
72
|
+
return options.send(API, "delete", 2, { tasks: [ref] });
|
|
73
|
+
}
|
|
74
|
+
function runTask(ref) {
|
|
75
|
+
return options.send(API, "run", 2, { tasks: [ref] });
|
|
76
|
+
}
|
|
77
|
+
function find(task) {
|
|
78
|
+
return listTasks().then(function (data) {
|
|
79
|
+
if (!data || !Array.isArray(data.tasks)) throw fail("root_bad_response");
|
|
80
|
+
var named = data.tasks.filter(function (summary) { return summary && summary.name === task.name; });
|
|
81
|
+
if (named.length === 0) return { state: "missing" };
|
|
82
|
+
if (named.length > 1 || !isRootTask(named[0])) return { state: "conflict" };
|
|
83
|
+
var ref = { id: named[0].id, real_owner: named[0].real_owner };
|
|
84
|
+
return readTask(ref).then(function (detail) {
|
|
85
|
+
var script = detail && detail.extra && detail.extra.script;
|
|
86
|
+
if (!detail || String(detail.id) !== String(ref.id) || detail.name !== task.name
|
|
87
|
+
|| detail.owner !== "root" || detail.real_owner !== ref.real_owner || detail.enable !== false || detail.type !== "script" || typeof script !== "string") {
|
|
88
|
+
return { state: "conflict" };
|
|
89
|
+
}
|
|
90
|
+
if (script === task.script) return { state: "ready", ref: ref };
|
|
91
|
+
if (script.indexOf(task.header) === 0) return { state: "outdated", ref: ref };
|
|
92
|
+
return { state: "conflict" };
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function definition(task, user) {
|
|
97
|
+
var hours = [];
|
|
98
|
+
for (var h = 1; h <= 23; h++) hours.push(h);
|
|
99
|
+
return {
|
|
100
|
+
name: task.name,
|
|
101
|
+
real_owner: user,
|
|
102
|
+
owner: "root",
|
|
103
|
+
enable: false,
|
|
104
|
+
type: "script",
|
|
105
|
+
schedule: {
|
|
106
|
+
date_type: 0, week_day: "0,1,2,3,4,5,6", repeat_date: 1001, monthly_week: [], hour: 0, minute: 0,
|
|
107
|
+
repeat_hour: 0, repeat_min: 0, last_work_hour: 0,
|
|
108
|
+
repeat_min_store_config: [1, 5, 10, 15, 20, 30], repeat_hour_store_config: hours
|
|
109
|
+
},
|
|
110
|
+
extra: { notify_enable: false, script: task.script, notify_mail: "", notify_if_error: false }
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function newest(history) {
|
|
114
|
+
if (!Array.isArray(history)) throw fail("root_bad_response");
|
|
115
|
+
return history.reduce(function (latest, entry) {
|
|
116
|
+
return entry && (!latest || Number(entry.timestamp) > Number(latest.timestamp)) ? entry : latest;
|
|
117
|
+
}, null);
|
|
118
|
+
}
|
|
119
|
+
function history(ref) {
|
|
120
|
+
return options.send(API, "get_history_status_list", 1, { id: ref.id }).then(newest);
|
|
121
|
+
}
|
|
122
|
+
function waitForRun(ref, before, polls) {
|
|
123
|
+
return history(ref).then(function (latest) {
|
|
124
|
+
var finished = latest && (!before || latest.timestamp !== before.timestamp)
|
|
125
|
+
&& latest.exit_type !== "running" && latest.stop_time;
|
|
126
|
+
if (finished) {
|
|
127
|
+
if (latest.exit_code !== 0) throw fail("root_task_failed");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (polls <= 1) throw fail("root_timeout");
|
|
131
|
+
return options.wait(POLL_MS).then(function () { return waitForRun(ref, before, polls - 1); });
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function validTime(value) {
|
|
136
|
+
if (typeof value !== "string" || !/^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$/.test(value)) return false;
|
|
137
|
+
var normalized = value.replace(" ", "T") + "Z";
|
|
138
|
+
var date = new Date(normalized);
|
|
139
|
+
return !isNaN(date.getTime()) && date.toISOString().slice(0, 19) === normalized.slice(0, 19);
|
|
140
|
+
}
|
|
141
|
+
function bounded(call, deadline) {
|
|
142
|
+
// A poll count alone cannot bound a stalled DSM callback. Discard late replies.
|
|
143
|
+
return new Promise(function (resolve, reject) {
|
|
144
|
+
var remaining = deadline - Date.now();
|
|
145
|
+
if (remaining <= 0) { reject(fail("root_timeout")); return; }
|
|
146
|
+
var timer = setTimeout(function () { reject(fail("root_timeout")); }, remaining);
|
|
147
|
+
Promise.resolve().then(call).then(function (value) {
|
|
148
|
+
clearTimeout(timer);
|
|
149
|
+
if (Date.now() >= deadline) reject(fail("root_timeout"));
|
|
150
|
+
else resolve(value);
|
|
151
|
+
}, function (error) { clearTimeout(timer); reject(error); });
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function legacyHistory(ref, deadline) {
|
|
155
|
+
return bounded(function () { return options.send(API, "view", 1, { id: ref.id }); }, deadline).then(function (records) {
|
|
156
|
+
// DSM 7.1 omits data entirely when a task has never run.
|
|
157
|
+
if (records === undefined) records = [];
|
|
158
|
+
if (!Array.isArray(records) || records.length > 500) throw fail("root_bad_response");
|
|
159
|
+
var seen = {};
|
|
160
|
+
records.forEach(function (record) {
|
|
161
|
+
if (!record || !validTime(record.start_time) || typeof record.stop_time !== "string"
|
|
162
|
+
|| (record.stop_time && (!validTime(record.stop_time) || record.stop_time < record.start_time))
|
|
163
|
+
|| typeof record.exit_type !== "string" || !(typeof record.exit_code === "number" && isFinite(record.exit_code) && Math.floor(record.exit_code) === record.exit_code)
|
|
164
|
+
|| seen[record.start_time]) throw fail("root_bad_response");
|
|
165
|
+
seen[record.start_time] = true;
|
|
166
|
+
});
|
|
167
|
+
return records;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function waitForLegacyRun(ref, before, deadline, polls) {
|
|
171
|
+
return legacyHistory(ref, deadline).then(function (records) {
|
|
172
|
+
var previous = before.map(function (record) { return record.start_time; });
|
|
173
|
+
var newestBefore = previous.slice().sort().pop();
|
|
174
|
+
var fresh = records.filter(function (record) { return previous.indexOf(record.start_time) === -1; });
|
|
175
|
+
if (fresh.length > 1) throw fail("root_bad_response");
|
|
176
|
+
var latest = fresh[0];
|
|
177
|
+
if (latest && (!newestBefore || latest.start_time > newestBefore) && latest.stop_time) {
|
|
178
|
+
if (latest.exit_code !== 0 || latest.exit_type !== "normal") throw fail("root_task_failed");
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (polls <= 1 || Date.now() >= deadline) throw fail("root_timeout");
|
|
182
|
+
return options.wait(Math.min(POLL_MS, deadline - Date.now())).then(function () {
|
|
183
|
+
return waitForLegacyRun(ref, before, deadline, polls - 1);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function execute(task, verified) {
|
|
188
|
+
if (!legacy) {
|
|
189
|
+
return history(verified.ref).then(function (before) {
|
|
190
|
+
return runTask(verified.ref).then(function () { return waitForRun(verified.ref, before, POLLS); });
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
var deadline = Date.now() + POLL_MS * POLLS;
|
|
194
|
+
return legacyHistory(verified.ref, deadline).then(function (before) {
|
|
195
|
+
if (before.some(function (record) { return !record.stop_time || record.exit_type === "running"; })) throw fail("root_not_ready");
|
|
196
|
+
// Mutating calls are sent exactly once, never retried with another profile.
|
|
197
|
+
return bounded(function () { return runTask(verified.ref); }, deadline).then(function () {
|
|
198
|
+
return waitForLegacyRun(verified.ref, before, deadline, POLLS);
|
|
199
|
+
});
|
|
200
|
+
}).then(function () {
|
|
201
|
+
return bounded(function () { return find(task); }, deadline).then(function (after) {
|
|
202
|
+
if (after.state !== "ready" || String(after.ref.id) !== String(verified.ref.id)) throw fail("root_verify_failed");
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function performRun(op) {
|
|
207
|
+
return begin(op).then(function (task) {
|
|
208
|
+
return find(task).then(function (found) {
|
|
209
|
+
if (found.state !== "ready") throw fail("root_not_ready");
|
|
210
|
+
var consent = op === "helper_repair" ? options.confirmPassword() : Promise.resolve({ confirmed: true });
|
|
211
|
+
return consent.then(function (confirmation) {
|
|
212
|
+
if (!confirmation || confirmation.confirmed !== true) throw fail("root_cancelled");
|
|
213
|
+
return op === "helper_repair" ? begin(op).then(find) : found;
|
|
214
|
+
}).then(function (verified) {
|
|
215
|
+
if (verified.state !== "ready") throw fail("root_not_ready");
|
|
216
|
+
return execute(task, verified);
|
|
217
|
+
});
|
|
218
|
+
}).then(function () { return readOutput(task); });
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
state: function (op) {
|
|
224
|
+
return begin(op).then(find).then(function (found) { return found.state; });
|
|
225
|
+
},
|
|
226
|
+
authorize: function (op) {
|
|
227
|
+
return begin(op).then(function (task) {
|
|
228
|
+
return find(task).then(function (found) {
|
|
229
|
+
if (found.state === "ready") return "ready";
|
|
230
|
+
if (found.state === "conflict") throw fail("root_conflict");
|
|
231
|
+
return options.confirmPassword().then(function (confirmation) {
|
|
232
|
+
if (!confirmation || confirmation.confirmed !== true) return "cancelled";
|
|
233
|
+
if (!legacy && (typeof confirmation.token !== "string" || !confirmation.token)) throw fail("root_verify_failed");
|
|
234
|
+
var params = definition(task, options.user().name);
|
|
235
|
+
if (!legacy) params.SynoConfirmPWToken = confirmation.token;
|
|
236
|
+
// Re-read after the legacy client-side dialog before replacing a task.
|
|
237
|
+
var checked = legacy ? begin(op).then(find) : Promise.resolve(found);
|
|
238
|
+
return checked.then(function (current) {
|
|
239
|
+
if (current.state === "conflict") throw fail("root_conflict");
|
|
240
|
+
if (current.state === "ready") return;
|
|
241
|
+
var replaced = current.state === "outdated" ? deleteTask(current.ref) : Promise.resolve();
|
|
242
|
+
return replaced.then(function () { return createTask(params); });
|
|
243
|
+
}).then(function () {
|
|
244
|
+
return find(task);
|
|
245
|
+
}).then(function (after) {
|
|
246
|
+
if (after.state !== "ready") throw fail("root_verify_failed");
|
|
247
|
+
return "ready";
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
},
|
|
253
|
+
run: function (op) {
|
|
254
|
+
if (!Object.prototype.hasOwnProperty.call(byOp, op)) return Promise.reject(fail("root_unknown_op"));
|
|
255
|
+
if (options.user().admin !== true) return Promise.reject(fail("admin_required"));
|
|
256
|
+
if (running[op]) return running[op];
|
|
257
|
+
var pending = performRun(op).then(function (value) {
|
|
258
|
+
delete running[op];
|
|
259
|
+
return value;
|
|
260
|
+
}, function (error) { delete running[op]; throw error; });
|
|
261
|
+
running[op] = pending;
|
|
262
|
+
return pending;
|
|
263
|
+
},
|
|
264
|
+
remove: function (op) {
|
|
265
|
+
return begin(op).then(function (task) {
|
|
266
|
+
return find(task).then(function (found) {
|
|
267
|
+
if (found.state === "missing") return "missing";
|
|
268
|
+
if (found.state === "conflict") throw fail("root_conflict");
|
|
269
|
+
return deleteTask(found.ref).then(function () {
|
|
270
|
+
return find(task);
|
|
271
|
+
}).then(function (after) {
|
|
272
|
+
if (after.state !== "missing") throw fail("root_verify_failed");
|
|
273
|
+
return "missing";
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
},
|
|
278
|
+
output: function (op) {
|
|
279
|
+
return begin(op).then(readOutput);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}`;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {computed,onMounted,onUnmounted,ref} from 'vue'
|
|
3
|
+
import {Button,Callout,Dialog,Surface} from '@synopackageland/ui'
|
|
4
|
+
import {ShieldCheck,TriangleAlert} from '@lucide/vue'
|
|
5
|
+
import type {AuthorizationStatus,Context} from '../context'
|
|
6
|
+
import {authorizationCopy} from './i18n'
|
|
7
|
+
const props=defineProps<{ctx:Context}>()
|
|
8
|
+
const emit=defineEmits<{ready:[]}>()
|
|
9
|
+
const state=ref<AuthorizationStatus>()
|
|
10
|
+
const pendingState=ref<AuthorizationStatus>()
|
|
11
|
+
const busy=ref(false)
|
|
12
|
+
const failure=ref(false)
|
|
13
|
+
const confirmRevoke=ref(false)
|
|
14
|
+
let disposed=false,sequence=0,pendingRecheck=false
|
|
15
|
+
let unsubscribe:(()=>void)|undefined
|
|
16
|
+
const text=computed(()=>authorizationCopy(props.ctx.locale,props.ctx.displayName||'__DISPLAY_NAME__'))
|
|
17
|
+
const ready=computed(()=>state.value?.state==='ready')
|
|
18
|
+
const shown=computed(()=>state.value||pendingState.value)
|
|
19
|
+
const required=computed(()=>shown.value?.state==='authorization_required')
|
|
20
|
+
const untrusted=computed(()=>['helper_untrusted','cli_untrusted'].includes(shown.value?.reason||''))
|
|
21
|
+
const canAuthorize=computed(()=>required.value&&!untrusted.value)
|
|
22
|
+
const unavailableKey=computed(()=>shown.value?.reason==='credential_not_ready'?'credential':'unavailable')
|
|
23
|
+
const title=computed(()=>!shown.value?text.value('checking'):required.value?text.value('title'):shown.value.state==='ready'?text.value('ready'):shown.value.state==='not_admin'?text.value('not_admin.title'):shown.value.state==='session_expired'?text.value('session_expired.title'):text.value(unavailableKey.value+'.title'))
|
|
24
|
+
const body=computed(()=>required.value?text.value('purpose'):shown.value?.state==='not_admin'?text.value('not_admin.body'):shown.value?.state==='session_expired'?text.value('session_expired.body'):shown.value?.state==='unavailable'?text.value(unavailableKey.value+'.body'):text.value('purpose'))
|
|
25
|
+
const reasons=['grant_missing','helper_untrusted','cli_untrusted','grant_incompatible','package_identity_changed','maintenance_task_missing','maintenance_task_mismatch','helper_health_rejected','capability_missing']
|
|
26
|
+
const reason=computed(()=>reasons.includes(shown.value?.reason||'')?shown.value!.reason!:'unknown')
|
|
27
|
+
const next=computed(()=>untrusted.value?'next.reinstall':reason.value==='grant_missing'?'next.install':reason.value==='grant_incompatible'?'next.update':'next.repair')
|
|
28
|
+
async function action(method:'status'|'request'|'revoke'){
|
|
29
|
+
if(busy.value||disposed)return
|
|
30
|
+
const current=++sequence
|
|
31
|
+
pendingState.value=method==='request'&&state.value?.state==='authorization_required'?state.value:undefined
|
|
32
|
+
busy.value=true;failure.value=false
|
|
33
|
+
// Mutation/revalidation immediately hides application content. A request
|
|
34
|
+
// keeps its prior reason visible while DSM's confirmation is pending.
|
|
35
|
+
state.value=undefined
|
|
36
|
+
try {
|
|
37
|
+
const result=await props.ctx.authorization?.[method]()
|
|
38
|
+
if(disposed||current!==sequence)return
|
|
39
|
+
if(!result||result.command!=='authorization'||result.schema_version!==1||!['ready','authorization_required','not_admin','session_expired','unavailable'].includes(result.state))throw Error('invalid status')
|
|
40
|
+
state.value=result
|
|
41
|
+
if(result.state==='ready')emit('ready')
|
|
42
|
+
} catch(e){if(!disposed&¤t===sequence){
|
|
43
|
+
state.value=(e as {code?:string}).code==='session_required'?{command:'authorization',schema_version:1,state:'session_expired',reason:'session_expired'}:pendingState.value||{command:'authorization',schema_version:1,state:'unavailable',reason:'service_unavailable'}
|
|
44
|
+
failure.value=true
|
|
45
|
+
}} finally{if(!disposed&¤t===sequence){busy.value=false;pendingState.value=undefined;if(pendingRecheck){pendingRecheck=false;void action('status')}}}
|
|
46
|
+
}
|
|
47
|
+
onMounted(()=>{unsubscribe=props.ctx.reopen(()=>{if(busy.value)pendingRecheck=true;else void action('status')});void action('status')})
|
|
48
|
+
onUnmounted(()=>{disposed=true;sequence++;if(typeof unsubscribe==='function')unsubscribe()})
|
|
49
|
+
</script>
|
|
50
|
+
<template>
|
|
51
|
+
<p class="authorization-announcement" role="status" aria-live="polite" aria-atomic="true">{{ busy ? text('authorizing') : title }}</p>
|
|
52
|
+
<div v-if="!ready" class="authorization-view" :aria-busy="busy" :data-authorization-state="state?.state || 'checking'">
|
|
53
|
+
<Surface class="authorization-card" padding="lg" raised :aria-label="title">
|
|
54
|
+
<span class="authorization-icon" aria-hidden="true"><component :is="shown?.state==='unavailable'||shown?.state==='session_expired'?TriangleAlert:ShieldCheck" class="authorization-icon-glyph" /></span>
|
|
55
|
+
<h1 class="authorization-title">{{ title }}</h1>
|
|
56
|
+
<p class="authorization-body">{{ body }}</p>
|
|
57
|
+
<Callout v-if="required" class="authorization-status" :tone="reason==='grant_missing'?'info':'warning'" :title="text('reason.'+reason)">{{ text(next) }}</Callout>
|
|
58
|
+
<Callout v-if="failure" class="authorization-failed" tone="warning">{{ text('failed') }}</Callout>
|
|
59
|
+
<div class="authorization-actions">
|
|
60
|
+
<Button v-if="canAuthorize" variant="primary" :loading="busy" :disabled="busy" @click="action('request')">{{ busy ? text('authorizing') : text('authorize') }}</Button>
|
|
61
|
+
<Button v-else variant="primary" :loading="busy" :disabled="busy" @click="action('status')">{{ busy ? text('checking') : text('retry') }}</Button>
|
|
62
|
+
</div>
|
|
63
|
+
</Surface>
|
|
64
|
+
</div>
|
|
65
|
+
<div v-else class="authorization-authorized">
|
|
66
|
+
<div class="authorization-ready"><ShieldCheck :size="16" aria-hidden="true"/><span>{{ text('ready') }}</span><Button v-if="ctx.authorization?.canManage?.()" variant="secondary" :disabled="busy" @click="confirmRevoke=true">{{ text('revoke') }}</Button></div>
|
|
67
|
+
<slot />
|
|
68
|
+
</div>
|
|
69
|
+
<Dialog v-model:open="confirmRevoke" :title="text('revoke.title')" :description="text('revoke.body')">
|
|
70
|
+
<template #footer><Button variant="secondary" @click="confirmRevoke=false">{{ text('cancel') }}</Button><Button variant="danger" :disabled="busy" @click="confirmRevoke=false;action('revoke')">{{ text('revoke') }}</Button></template>
|
|
71
|
+
</Dialog>
|
|
72
|
+
</template>
|
|
73
|
+
<style scoped>
|
|
74
|
+
.authorization-announcement{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
|
|
75
|
+
/* T0156: auto margins center the whole card but preserve top scrolling in a short window. */
|
|
76
|
+
.authorization-view{display:flex;align-items:flex-start;justify-content:center;min-height:100%;height:100%;box-sizing:border-box;padding:24px;overflow-y:auto;color:var(--ui-text);font-family:var(--ui-font-sans)}
|
|
77
|
+
.authorization-card{display:flex;flex-direction:column;align-items:center;gap:12px;width:100%;max-width:28rem;margin:auto;text-align:center}
|
|
78
|
+
.authorization-icon{display:flex;align-items:center;justify-content:center;width:48px;height:48px;flex-shrink:0;border-radius:9999px;background:var(--ui-primary-soft);color:var(--ui-primary)}
|
|
79
|
+
.authorization-icon-glyph{width:24px;height:24px}
|
|
80
|
+
.authorization-title{font-size:16px;font-weight:600;line-height:1.4;color:var(--ui-text);margin:0;text-wrap:balance}
|
|
81
|
+
.authorization-body{max-width:24rem;font-size:13px;line-height:1.6;color:var(--ui-text);margin:0;text-align:center;text-wrap:pretty}
|
|
82
|
+
.authorization-status,.authorization-failed{width:100%;text-align:left}
|
|
83
|
+
/* Essential recovery text uses the full foreground; DSM's muted token is below 4.5:1. */
|
|
84
|
+
.authorization-status,.authorization-failed{color:var(--ui-text)}
|
|
85
|
+
.authorization-actions{display:flex;justify-content:center;width:100%;margin-top:4px}
|
|
86
|
+
.authorization-authorized{display:flex;flex-direction:column;height:100%;min-height:0}.authorization-authorized :deep(.app-shell){flex:1;min-height:0;height:auto}
|
|
87
|
+
.authorization-ready{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:8px 16px;color:var(--ui-text);font:13px var(--ui-font-sans)}
|
|
88
|
+
@media(max-width:480px){.authorization-view{padding:16px}}
|
|
89
|
+
</style>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"authorization.title": "{package} benötigt Ihre Erlaubnis",
|
|
3
|
+
"authorization.purpose": "Dieses Paket benötigt eine Genehmigung für feste Root-Operationen. DSM verarbeitet Ihr Passwort; das Paket sieht oder speichert es nicht.",
|
|
4
|
+
"authorization.reason.grant_missing": "Root-Operationen sind noch nicht autorisiert.",
|
|
5
|
+
"authorization.reason.helper_untrusted": "Dem privilegierten Helfer konnte nicht vertraut werden.",
|
|
6
|
+
"authorization.reason.cli_untrusted": "Dem privilegierten Befehl konnte nicht vertraut werden.",
|
|
7
|
+
"authorization.reason.grant_incompatible": "Der privilegierte Helfer muss aktualisiert werden.",
|
|
8
|
+
"authorization.reason.package_identity_changed": "Das Paketkonto hat sich geändert.",
|
|
9
|
+
"authorization.reason.maintenance_task_missing": "Die Wartungsaufgabe fehlt.",
|
|
10
|
+
"authorization.reason.maintenance_task_mismatch": "Die Wartungsaufgabe passt nicht mehr zur Berechtigung.",
|
|
11
|
+
"authorization.reason.helper_health_rejected": "Der privilegierte Helfer hat seine Integritätsprüfung nicht bestanden.",
|
|
12
|
+
"authorization.reason.capability_missing": "Dem privilegierten Helfer fehlt eine erforderliche Fähigkeit.",
|
|
13
|
+
"authorization.reason.unknown": "Die Berechtigung fehlt oder ist nicht mehr gültig.",
|
|
14
|
+
"authorization.next.install": "Wählen Sie Autorisieren und bestätigen Sie Ihr DSM-Passwort, um diesen Build zu genehmigen.",
|
|
15
|
+
"authorization.next.update": "Dieser Build benötigt eine neue Autorisierung. Wählen Sie Autorisieren und bestätigen Sie Ihr DSM-Passwort.",
|
|
16
|
+
"authorization.next.repair": "Prüfen Sie die Aufgaben dieses Pakets im Aufgabenplaner und versuchen Sie es erneut.",
|
|
17
|
+
"authorization.authorize": "Berechtigen",
|
|
18
|
+
"authorization.authorizing": "Warten auf DSM…",
|
|
19
|
+
"authorization.failed": "Die Berechtigung wurde nicht abgeschlossen. Sie können es erneut versuchen.",
|
|
20
|
+
"authorization.unavailable.title": "Berechtigung konnte nicht geprüft werden",
|
|
21
|
+
"authorization.unavailable.body": "Das ist meist vorübergehend. Versuchen Sie es erneut; {package} fragt nicht nach Ihrem Passwort.",
|
|
22
|
+
"authorization.credential.title": "Die DSM-Anmeldeinformationen sind nicht bereit",
|
|
23
|
+
"authorization.credential.body": "Diese DSM-Sitzung wurde ohne Anmeldung mit Kennwort wiederhergestellt, daher kann DSM die Anfragen von {package} nicht bestätigen. Melden Sie sich bei DSM ab, melden Sie sich erneut mit Ihrem Kennwort an und öffnen Sie dann {package} erneut.",
|
|
24
|
+
"authorization.retry": "Erneut versuchen",
|
|
25
|
+
"authorization.next.reinstall": "Die installierten Dateien stimmen nicht mit diesem Build überein. Installieren Sie das Paket aus einer vertrauenswürdigen Quelle neu.",
|
|
26
|
+
"authorization.not_admin.title": "Administrator erforderlich",
|
|
27
|
+
"authorization.not_admin.body": "Bitten Sie einen DSM-Administrator, dieses Paket zu öffnen und zu autorisieren.",
|
|
28
|
+
"authorization.session_expired.title": "Sitzung abgelaufen",
|
|
29
|
+
"authorization.session_expired.body": "Melden Sie sich erneut bei DSM an und öffnen Sie dieses Paket wieder.",
|
|
30
|
+
"authorization.ready": "Autorisiert",
|
|
31
|
+
"authorization.revoke": "Autorisierung widerrufen",
|
|
32
|
+
"authorization.revoke.title": "Autorisierung widerrufen?",
|
|
33
|
+
"authorization.revoke.body": "Neuer Helper-Zugriff wird deaktiviert. Paketdaten und bisherige Ergebnisse bleiben erhalten.",
|
|
34
|
+
"authorization.cancel": "Abbrechen",
|
|
35
|
+
"authorization.checking": "Autorisierung wird geprüft…"
|
|
36
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"authorization.title": "{package} needs your permission",
|
|
3
|
+
"authorization.purpose": "This package needs approval for its fixed root operations. DSM handles your password; the package never sees or stores it.",
|
|
4
|
+
"authorization.reason.grant_missing": "Root operations are not authorized yet.",
|
|
5
|
+
"authorization.reason.helper_untrusted": "The privileged helper could not be trusted.",
|
|
6
|
+
"authorization.reason.cli_untrusted": "The privileged command could not be trusted.",
|
|
7
|
+
"authorization.reason.grant_incompatible": "The privileged helper needs an update.",
|
|
8
|
+
"authorization.reason.package_identity_changed": "The package account changed.",
|
|
9
|
+
"authorization.reason.maintenance_task_missing": "The maintenance task is missing.",
|
|
10
|
+
"authorization.reason.maintenance_task_mismatch": "The maintenance task no longer matches the authorization.",
|
|
11
|
+
"authorization.reason.helper_health_rejected": "The privileged helper did not pass its health check.",
|
|
12
|
+
"authorization.reason.capability_missing": "The privileged helper lacks a required capability.",
|
|
13
|
+
"authorization.reason.unknown": "The authorization is missing or no longer valid.",
|
|
14
|
+
"authorization.next.install": "Choose Authorize and confirm your DSM password to approve this build.",
|
|
15
|
+
"authorization.next.update": "This build needs fresh authorization. Choose Authorize and confirm your DSM password.",
|
|
16
|
+
"authorization.next.repair": "Review this package and its tasks in Task Scheduler, then retry.",
|
|
17
|
+
"authorization.authorize": "Authorize",
|
|
18
|
+
"authorization.authorizing": "Waiting for DSM…",
|
|
19
|
+
"authorization.failed": "Authorization did not finish. You can try again.",
|
|
20
|
+
"authorization.unavailable.title": "Authorization could not be checked",
|
|
21
|
+
"authorization.unavailable.body": "This is usually temporary. Try again — {package} will not ask for your password.",
|
|
22
|
+
"authorization.credential.title": "DSM's sign-in credential is not ready",
|
|
23
|
+
"authorization.credential.body": "This DSM session was restored without a password sign-in, so DSM cannot vouch for {package}'s requests. Sign out of DSM, sign in again with your password, then reopen {package}.",
|
|
24
|
+
"authorization.retry": "Try again",
|
|
25
|
+
"authorization.next.reinstall": "The installed files do not match this build. Reinstall the package from a trusted source, then retry.",
|
|
26
|
+
"authorization.not_admin.title": "Administrator needed",
|
|
27
|
+
"authorization.not_admin.body": "Ask a DSM administrator to open this package and authorize it.",
|
|
28
|
+
"authorization.session_expired.title": "Session expired",
|
|
29
|
+
"authorization.session_expired.body": "Sign in to DSM again, then reopen this package.",
|
|
30
|
+
"authorization.ready": "Authorized",
|
|
31
|
+
"authorization.revoke": "Revoke authorization",
|
|
32
|
+
"authorization.revoke.title": "Revoke authorization?",
|
|
33
|
+
"authorization.revoke.body": "New helper access will be disabled. Package data and past results are retained.",
|
|
34
|
+
"authorization.cancel": "Cancel",
|
|
35
|
+
"authorization.checking": "Checking authorization…"
|
|
36
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"authorization.title": "{package} necesita su permiso",
|
|
3
|
+
"authorization.purpose": "Este paquete requiere aprobación para sus operaciones root fijas. DSM gestiona su contraseña; el paquete no la ve ni la almacena.",
|
|
4
|
+
"authorization.reason.grant_missing": "Las operaciones root aún no están autorizadas.",
|
|
5
|
+
"authorization.reason.helper_untrusted": "No se pudo confiar en el asistente privilegiado.",
|
|
6
|
+
"authorization.reason.cli_untrusted": "No se pudo confiar en el comando privilegiado.",
|
|
7
|
+
"authorization.reason.grant_incompatible": "El asistente privilegiado necesita una actualización.",
|
|
8
|
+
"authorization.reason.package_identity_changed": "La cuenta del paquete ha cambiado.",
|
|
9
|
+
"authorization.reason.maintenance_task_missing": "Falta la tarea de mantenimiento.",
|
|
10
|
+
"authorization.reason.maintenance_task_mismatch": "La tarea de mantenimiento ya no coincide con la autorización.",
|
|
11
|
+
"authorization.reason.helper_health_rejected": "El asistente privilegiado no superó su comprobación de estado.",
|
|
12
|
+
"authorization.reason.capability_missing": "Al asistente privilegiado le falta una capacidad necesaria.",
|
|
13
|
+
"authorization.reason.unknown": "La autorización falta o ya no es válida.",
|
|
14
|
+
"authorization.next.install": "Elija Autorizar y confirme su contraseña de DSM para aprobar esta versión.",
|
|
15
|
+
"authorization.next.update": "Esta versión requiere una nueva autorización. Elija Autorizar y confirme su contraseña de DSM.",
|
|
16
|
+
"authorization.next.repair": "Revise las tareas de este paquete en el Programador de tareas y vuelva a intentarlo.",
|
|
17
|
+
"authorization.authorize": "Autorizar",
|
|
18
|
+
"authorization.authorizing": "Esperando a DSM…",
|
|
19
|
+
"authorization.failed": "La autorización no se completó. Puede intentarlo de nuevo.",
|
|
20
|
+
"authorization.unavailable.title": "No se pudo comprobar la autorización",
|
|
21
|
+
"authorization.unavailable.body": "Suele ser temporal. Inténtelo de nuevo; {package} no le pedirá la contraseña.",
|
|
22
|
+
"authorization.credential.title": "Las credenciales de inicio de sesión de DSM no están listas",
|
|
23
|
+
"authorization.credential.body": "Esta sesión de DSM se restauró sin iniciar sesión con contraseña, por lo que DSM no puede avalar las solicitudes de {package}. Cierre sesión en DSM, inicie sesión de nuevo con su contraseña y vuelva a abrir {package}.",
|
|
24
|
+
"authorization.retry": "Reintentar",
|
|
25
|
+
"authorization.next.reinstall": "Los archivos instalados no coinciden con esta versión. Reinstale el paquete desde una fuente fiable y vuelva a intentarlo.",
|
|
26
|
+
"authorization.not_admin.title": "Se requiere un administrador",
|
|
27
|
+
"authorization.not_admin.body": "Pida a un administrador de DSM que abra y autorice este paquete.",
|
|
28
|
+
"authorization.session_expired.title": "Sesión caducada",
|
|
29
|
+
"authorization.session_expired.body": "Inicie sesión de nuevo en DSM y vuelva a abrir este paquete.",
|
|
30
|
+
"authorization.ready": "Autorizado",
|
|
31
|
+
"authorization.revoke": "Revocar autorización",
|
|
32
|
+
"authorization.revoke.title": "¿Revocar autorización?",
|
|
33
|
+
"authorization.revoke.body": "Se desactivarán los nuevos accesos del helper. Se conservan los datos del paquete y los resultados anteriores.",
|
|
34
|
+
"authorization.cancel": "Cancelar",
|
|
35
|
+
"authorization.checking": "Comprobando autorización…"
|
|
36
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"authorization.title": "{package} demande votre autorisation",
|
|
3
|
+
"authorization.purpose": "Ce paquet nécessite une autorisation pour ses opérations root fixes. DSM traite votre mot de passe ; le paquet ne le voit ni ne le stocke.",
|
|
4
|
+
"authorization.reason.grant_missing": "Les opérations root ne sont pas encore autorisées.",
|
|
5
|
+
"authorization.reason.helper_untrusted": "L'assistant privilégié n'a pas pu être approuvé.",
|
|
6
|
+
"authorization.reason.cli_untrusted": "La commande privilégiée n'a pas pu être approuvée.",
|
|
7
|
+
"authorization.reason.grant_incompatible": "L'assistant privilégié doit être mis à jour.",
|
|
8
|
+
"authorization.reason.package_identity_changed": "Le compte du paquet a changé.",
|
|
9
|
+
"authorization.reason.maintenance_task_missing": "La tâche de maintenance est introuvable.",
|
|
10
|
+
"authorization.reason.maintenance_task_mismatch": "La tâche de maintenance ne correspond plus à l'autorisation.",
|
|
11
|
+
"authorization.reason.helper_health_rejected": "L'assistant privilégié n'a pas réussi son contrôle d'intégrité.",
|
|
12
|
+
"authorization.reason.capability_missing": "Il manque à l'assistant privilégié une capacité requise.",
|
|
13
|
+
"authorization.reason.unknown": "L'autorisation est absente ou n'est plus valide.",
|
|
14
|
+
"authorization.next.install": "Choisissez Autoriser et confirmez votre mot de passe DSM pour approuver cette version.",
|
|
15
|
+
"authorization.next.update": "Cette version nécessite une nouvelle autorisation. Choisissez Autoriser et confirmez votre mot de passe DSM.",
|
|
16
|
+
"authorization.next.repair": "Vérifiez les tâches de ce paquet dans le Planificateur de tâches, puis réessayez.",
|
|
17
|
+
"authorization.authorize": "Autoriser",
|
|
18
|
+
"authorization.authorizing": "En attente de DSM…",
|
|
19
|
+
"authorization.failed": "L'autorisation ne s'est pas terminée. Vous pouvez réessayer.",
|
|
20
|
+
"authorization.unavailable.title": "Impossible de vérifier l'autorisation",
|
|
21
|
+
"authorization.unavailable.body": "C'est généralement temporaire. Réessayez ; {package} ne demandera pas votre mot de passe.",
|
|
22
|
+
"authorization.credential.title": "Les identifiants de connexion DSM ne sont pas prêts",
|
|
23
|
+
"authorization.credential.body": "Cette session DSM a été restaurée sans connexion par mot de passe : DSM ne peut donc pas authentifier les requêtes de {package}. Déconnectez-vous de DSM, reconnectez-vous avec votre mot de passe, puis rouvrez {package}.",
|
|
24
|
+
"authorization.retry": "Réessayer",
|
|
25
|
+
"authorization.next.reinstall": "Les fichiers installés ne correspondent pas à cette version. Réinstallez le paquet depuis une source fiable, puis réessayez.",
|
|
26
|
+
"authorization.not_admin.title": "Administrateur requis",
|
|
27
|
+
"authorization.not_admin.body": "Demandez à un administrateur DSM d’ouvrir ce paquet et de l’autoriser.",
|
|
28
|
+
"authorization.session_expired.title": "Session expirée",
|
|
29
|
+
"authorization.session_expired.body": "Reconnectez-vous à DSM, puis rouvrez ce paquet.",
|
|
30
|
+
"authorization.ready": "Autorisé",
|
|
31
|
+
"authorization.revoke": "Révoquer l’autorisation",
|
|
32
|
+
"authorization.revoke.title": "Révoquer l’autorisation ?",
|
|
33
|
+
"authorization.revoke.body": "Les nouveaux accès helper seront désactivés. Les données du paquet et les résultats précédents sont conservés.",
|
|
34
|
+
"authorization.cancel": "Annuler",
|
|
35
|
+
"authorization.checking": "Vérification de l’autorisation…"
|
|
36
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import en from './en.json'
|
|
2
|
+
import zhTW from './zh-TW.json'
|
|
3
|
+
import zhCN from './zh-CN.json'
|
|
4
|
+
import ja from './ja.json'
|
|
5
|
+
import ko from './ko.json'
|
|
6
|
+
import de from './de.json'
|
|
7
|
+
import fr from './fr.json'
|
|
8
|
+
import es from './es.json'
|
|
9
|
+
import it from './it.json'
|
|
10
|
+
const catalogs:Record<string,Record<string,string>>={en,'zh-TW':zhTW,'zh-CN':zhCN,ja,ko,de,fr,es,it}
|
|
11
|
+
/** Copy follows Reclaim T0156; root-task semantics are template-owned. */
|
|
12
|
+
export function authorizationCopy(locale:string,packageName:string){
|
|
13
|
+
const catalog={...en,...catalogs[locale]}
|
|
14
|
+
return (key:string)=>((catalog as Record<string,string>)[`authorization.${key}`]||key).replaceAll('{package}',packageName)
|
|
15
|
+
}
|