@tachybase/module-backup 1.5.1 → 1.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/dist/client/components/BackupProgressCell.d.ts +36 -0
- package/dist/client/hooks/useBackupProgress.d.ts +53 -0
- package/dist/client/hooks/useDownloadProgress.d.ts +40 -0
- package/dist/client/index.js +3 -3
- package/dist/externalVersion.js +5 -5
- package/dist/locale/en-US.json +7 -0
- package/dist/locale/ja-JP.d.ts +6 -0
- package/dist/locale/ja-JP.js +6 -0
- package/dist/locale/ko_KR.json +6 -0
- package/dist/locale/pt-BR.d.ts +6 -0
- package/dist/locale/pt-BR.js +6 -0
- package/dist/locale/zh-CN.json +7 -0
- package/dist/node_modules/@hapi/topo/lib/index.js +1 -1
- package/dist/node_modules/@hapi/topo/package.json +1 -1
- package/dist/node_modules/archiver/package.json +1 -1
- package/dist/node_modules/cron-parser/package.json +1 -1
- package/dist/node_modules/semver/package.json +1 -1
- package/dist/node_modules/yauzl/package.json +1 -1
- package/dist/server/dumper.d.ts +9 -3
- package/dist/server/dumper.js +78 -15
- package/dist/server/progress-tracker.d.ts +76 -0
- package/dist/server/progress-tracker.js +286 -0
- package/dist/server/resourcers/backup-files.js +30 -27
- package/dist/server/server.d.ts +1 -0
- package/dist/server/server.js +49 -1
- package/package.json +8 -8
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var progress_tracker_exports = {};
|
|
29
|
+
__export(progress_tracker_exports, {
|
|
30
|
+
ProgressManager: () => ProgressManager
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(progress_tracker_exports);
|
|
33
|
+
var import_promises = __toESM(require("node:fs/promises"));
|
|
34
|
+
var import_node_path = __toESM(require("node:path"));
|
|
35
|
+
var import_server = require("@tego/server");
|
|
36
|
+
const _ProgressManager = class _ProgressManager {
|
|
37
|
+
constructor(backupStorageDir, workDir, app) {
|
|
38
|
+
this.backupStorageDir = backupStorageDir;
|
|
39
|
+
this.workDir = workDir;
|
|
40
|
+
this.app = app;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 获取进度文件路径(静态方法,用于不需要实例的场景)
|
|
44
|
+
*/
|
|
45
|
+
static getProgressFilePath(filePath) {
|
|
46
|
+
return filePath + ".progress";
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 获取进度文件路径
|
|
50
|
+
*/
|
|
51
|
+
progressFilePath(fileName, appName) {
|
|
52
|
+
const progressFile = fileName + ".progress";
|
|
53
|
+
const dirname = this.backupStorageDir(appName);
|
|
54
|
+
return import_node_path.default.resolve(dirname, progressFile);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 通过 WebSocket 推送进度更新
|
|
58
|
+
* @param fileName 备份文件名
|
|
59
|
+
* @param progress 进度信息
|
|
60
|
+
* @param userId 用户ID(可选,如果未提供则不会推送,适用于自动备份场景)
|
|
61
|
+
* @param appName 应用名称
|
|
62
|
+
*/
|
|
63
|
+
pushProgressViaWebSocket(fileName, progress, userId, appName) {
|
|
64
|
+
var _a, _b, _c, _d, _e;
|
|
65
|
+
if (!this.app) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!userId || userId <= 0) {
|
|
69
|
+
if ((_a = this.app) == null ? void 0 : _a.logger) {
|
|
70
|
+
this.app.logger.debug(`[ProgressManager] Skipping WebSocket push for ${fileName}: no userId`);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const gateway = import_server.Gateway.getInstance();
|
|
76
|
+
const ws = gateway["wsServer"];
|
|
77
|
+
if (!ws) {
|
|
78
|
+
if (!_ProgressManager.wsUnavailableWarned) {
|
|
79
|
+
_ProgressManager.wsUnavailableWarned = true;
|
|
80
|
+
if ((_b = this.app) == null ? void 0 : _b.logger) {
|
|
81
|
+
this.app.logger.warn(
|
|
82
|
+
"[ProgressManager] WebSocket server not available (this is normal in worker threads, progress will be saved to file only)"
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const finalAppName = appName || this.app.name;
|
|
89
|
+
const tagPrefix = `app:${finalAppName}`;
|
|
90
|
+
const tagValue = `${userId}`;
|
|
91
|
+
const expectedTag = `${tagPrefix}#${tagValue}`;
|
|
92
|
+
if ((_c = this.app) == null ? void 0 : _c.logger) {
|
|
93
|
+
const matchingConnections = Array.from(ws.webSocketClients.values()).filter((client) => {
|
|
94
|
+
var _a2, _b2;
|
|
95
|
+
return (_b2 = (_a2 = client.tags) == null ? void 0 : _a2.has) == null ? void 0 : _b2.call(_a2, expectedTag);
|
|
96
|
+
});
|
|
97
|
+
this.app.logger.debug(
|
|
98
|
+
`[ProgressManager] Sending backup progress via WebSocket: fileName=${fileName}, userId=${userId}, appName=${finalAppName}, tag=${expectedTag}, progress=${progress.percent}%, matchingConnections=${matchingConnections.length}`
|
|
99
|
+
);
|
|
100
|
+
if (matchingConnections.length === 0) {
|
|
101
|
+
const allTags = Array.from(ws.webSocketClients.values()).flatMap((client) => Array.from(client.tags || [])).filter((tag) => typeof tag === "string" && tag.startsWith("app:"));
|
|
102
|
+
this.app.logger.warn(
|
|
103
|
+
`[ProgressManager] No matching connections found for tag ${expectedTag}. Available tags: ${allTags.join(", ")}`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
ws.sendToConnectionsByTag(tagPrefix, tagValue, {
|
|
109
|
+
type: "backup:progress",
|
|
110
|
+
payload: {
|
|
111
|
+
fileName,
|
|
112
|
+
progress
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
} catch (sendError) {
|
|
116
|
+
if ((_d = this.app) == null ? void 0 : _d.logger) {
|
|
117
|
+
this.app.logger.error(
|
|
118
|
+
`[ProgressManager] Failed to send WebSocket message: tagPrefix=${tagPrefix}, tagValue=${tagValue}, error=`,
|
|
119
|
+
sendError
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
throw sendError;
|
|
123
|
+
}
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if ((_e = this.app) == null ? void 0 : _e.logger) {
|
|
126
|
+
this.app.logger.warn(`[ProgressManager] WebSocket push error for ${fileName} (userId=${userId}):`, error);
|
|
127
|
+
} else {
|
|
128
|
+
console.error(`[ProgressManager] WebSocket push error for ${fileName} (userId=${userId}):`, error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 写入进度信息
|
|
134
|
+
*/
|
|
135
|
+
async writeProgress(fileName, progress, appName, userId) {
|
|
136
|
+
const filePath = this.progressFilePath(fileName, appName);
|
|
137
|
+
await import_promises.default.writeFile(filePath, JSON.stringify(progress), "utf8");
|
|
138
|
+
this.pushProgressViaWebSocket(fileName, progress, userId, appName);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 读取进度信息
|
|
142
|
+
*/
|
|
143
|
+
async readProgress(fileName, appName) {
|
|
144
|
+
const filePath = this.progressFilePath(fileName, appName);
|
|
145
|
+
try {
|
|
146
|
+
const content = await import_promises.default.readFile(filePath, "utf8");
|
|
147
|
+
return JSON.parse(content);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if (error.code === "ENOENT") {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* 清理进度文件
|
|
157
|
+
*/
|
|
158
|
+
async cleanProgressFile(fileName, appName) {
|
|
159
|
+
const filePath = this.progressFilePath(fileName, appName);
|
|
160
|
+
try {
|
|
161
|
+
await import_promises.default.unlink(filePath);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (error.code !== "ENOENT") {
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* 创建进度跟踪器
|
|
170
|
+
*/
|
|
171
|
+
createProgressTracker(backupFileName, appName, userId) {
|
|
172
|
+
return {
|
|
173
|
+
/**
|
|
174
|
+
* 更新进度
|
|
175
|
+
*/
|
|
176
|
+
update: async (percent, currentStep) => {
|
|
177
|
+
await this.writeProgress(backupFileName, { percent, currentStep }, appName, userId);
|
|
178
|
+
},
|
|
179
|
+
/**
|
|
180
|
+
* 计算集合备份阶段的进度 (5-70%)
|
|
181
|
+
*/
|
|
182
|
+
getCollectionProgress: (currentIndex, totalCollections) => {
|
|
183
|
+
return 5 + Math.floor((currentIndex + 1) / totalCollections * 65);
|
|
184
|
+
},
|
|
185
|
+
/**
|
|
186
|
+
* 计算数据库内容备份阶段的进度 (80-88%)
|
|
187
|
+
*/
|
|
188
|
+
getDbContentProgress: (processedCollections, totalCollections) => {
|
|
189
|
+
if (totalCollections === 0) return 80;
|
|
190
|
+
return 80 + Math.floor(processedCollections / totalCollections * 8);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* 统计目录中的文件总数
|
|
196
|
+
*/
|
|
197
|
+
async countFiles(dir) {
|
|
198
|
+
let count = 0;
|
|
199
|
+
try {
|
|
200
|
+
const entries = await import_promises.default.readdir(dir, { withFileTypes: true });
|
|
201
|
+
for (const entry of entries) {
|
|
202
|
+
const fullPath = import_node_path.default.join(dir, entry.name);
|
|
203
|
+
if (entry.isDirectory()) {
|
|
204
|
+
count += await this.countFiles(fullPath);
|
|
205
|
+
} else {
|
|
206
|
+
count++;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} catch (error) {
|
|
210
|
+
}
|
|
211
|
+
return count;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* 设置打包阶段的进度更新
|
|
215
|
+
*/
|
|
216
|
+
setupPackingProgress(archive, progressTracker) {
|
|
217
|
+
let processedEntries = 0;
|
|
218
|
+
let totalEntries = 0;
|
|
219
|
+
let startTime = Date.now();
|
|
220
|
+
let progressInterval = null;
|
|
221
|
+
let currentProgress = 90;
|
|
222
|
+
let isFinished = false;
|
|
223
|
+
this.countFiles(this.workDir).then((count) => {
|
|
224
|
+
totalEntries = count || 1;
|
|
225
|
+
}).catch(() => {
|
|
226
|
+
totalEntries = 1;
|
|
227
|
+
});
|
|
228
|
+
archive.on("entry", () => {
|
|
229
|
+
processedEntries++;
|
|
230
|
+
});
|
|
231
|
+
const handleFinish = async () => {
|
|
232
|
+
var _a;
|
|
233
|
+
if (isFinished) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
isFinished = true;
|
|
237
|
+
if (progressInterval) {
|
|
238
|
+
clearInterval(progressInterval);
|
|
239
|
+
progressInterval = null;
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
await progressTracker.update(99, "Packing backup file...");
|
|
243
|
+
} catch (error) {
|
|
244
|
+
if ((_a = this.app) == null ? void 0 : _a.logger) {
|
|
245
|
+
this.app.logger.warn("[ProgressManager] Failed to update progress after packing:", error);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
archive.on("end", handleFinish);
|
|
250
|
+
archive.on("finish", handleFinish);
|
|
251
|
+
progressInterval = setInterval(async () => {
|
|
252
|
+
if (isFinished) {
|
|
253
|
+
if (progressInterval) {
|
|
254
|
+
clearInterval(progressInterval);
|
|
255
|
+
progressInterval = null;
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const elapsed = Date.now() - startTime;
|
|
260
|
+
if (processedEntries > 0 && totalEntries > 0) {
|
|
261
|
+
const fileBasedProgress = 90 + Math.floor(processedEntries / totalEntries * 9);
|
|
262
|
+
const timeBasedProgress = Math.min(90 + Math.floor(elapsed / 6e4 * 9), 99);
|
|
263
|
+
currentProgress = Math.max(currentProgress, Math.min(fileBasedProgress, timeBasedProgress, 99));
|
|
264
|
+
} else {
|
|
265
|
+
currentProgress = Math.min(90 + Math.floor(elapsed / 6e4 * 9), 99);
|
|
266
|
+
}
|
|
267
|
+
if (currentProgress < 99) {
|
|
268
|
+
await progressTracker.update(currentProgress, "Packing backup file...");
|
|
269
|
+
}
|
|
270
|
+
}, 300);
|
|
271
|
+
return () => {
|
|
272
|
+
isFinished = true;
|
|
273
|
+
if (progressInterval) {
|
|
274
|
+
clearInterval(progressInterval);
|
|
275
|
+
progressInterval = null;
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
// 静态标志,用于避免重复警告 WebSocket 不可用(在 worker 线程中很常见)
|
|
281
|
+
_ProgressManager.wsUnavailableWarned = false;
|
|
282
|
+
let ProgressManager = _ProgressManager;
|
|
283
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
284
|
+
0 && (module.exports = {
|
|
285
|
+
ProgressManager
|
|
286
|
+
});
|
|
@@ -57,15 +57,15 @@ var backup_files_default = {
|
|
|
57
57
|
actions: {
|
|
58
58
|
async list(ctx, next) {
|
|
59
59
|
const { page = import_server.DEFAULT_PAGE, pageSize = import_server.DEFAULT_PER_PAGE } = ctx.action.params;
|
|
60
|
-
const dumper = new import_dumper.Dumper(ctx.
|
|
60
|
+
const dumper = new import_dumper.Dumper(ctx.tego);
|
|
61
61
|
const backupFiles = await dumper.allBackUpFilePaths({
|
|
62
62
|
includeInProgress: true,
|
|
63
|
-
appName: ctx.
|
|
63
|
+
appName: ctx.tego.name
|
|
64
64
|
});
|
|
65
65
|
const count = backupFiles.length;
|
|
66
66
|
const rows = await Promise.all(
|
|
67
67
|
backupFiles.slice((page - 1) * pageSize, page * pageSize).map(async (file) => {
|
|
68
|
-
return await import_dumper.Dumper.getFileStatus(file.endsWith(".lock") ? file.replace(".lock", "") : file);
|
|
68
|
+
return await import_dumper.Dumper.getFileStatus(file.endsWith(".lock") ? file.replace(".lock", "") : file, ctx.tego.name);
|
|
69
69
|
})
|
|
70
70
|
);
|
|
71
71
|
ctx.body = {
|
|
@@ -79,8 +79,8 @@ var backup_files_default = {
|
|
|
79
79
|
},
|
|
80
80
|
async get(ctx, next) {
|
|
81
81
|
const { filterByTk } = ctx.action.params;
|
|
82
|
-
const dumper = new import_dumper.Dumper(ctx.
|
|
83
|
-
const filePath = dumper.backUpFilePath(filterByTk, ctx.
|
|
82
|
+
const dumper = new import_dumper.Dumper(ctx.tego);
|
|
83
|
+
const filePath = dumper.backUpFilePath(filterByTk, ctx.tego.name);
|
|
84
84
|
async function sendError(message, status = 404) {
|
|
85
85
|
ctx.body = { status: "error", message };
|
|
86
86
|
ctx.status = status;
|
|
@@ -90,7 +90,7 @@ var backup_files_default = {
|
|
|
90
90
|
if (fileState.status !== "ok") {
|
|
91
91
|
await sendError(`Backup file ${filterByTk} not found`);
|
|
92
92
|
} else {
|
|
93
|
-
const restorer = new import_restorer.Restorer(ctx.
|
|
93
|
+
const restorer = new import_restorer.Restorer(ctx.tego, {
|
|
94
94
|
backUpFilePath: filePath
|
|
95
95
|
});
|
|
96
96
|
const restoreMeta = await restorer.parseBackupFile();
|
|
@@ -112,24 +112,26 @@ var backup_files_default = {
|
|
|
112
112
|
* @param next
|
|
113
113
|
*/
|
|
114
114
|
async create(ctx, next) {
|
|
115
|
-
var _a, _b;
|
|
115
|
+
var _a, _b, _c;
|
|
116
116
|
const data = ctx.request.body;
|
|
117
|
-
const app = ctx.
|
|
118
|
-
|
|
117
|
+
const app = ctx.tego;
|
|
118
|
+
const userId = (_a = ctx.state.currentUser) == null ? void 0 : _a.id;
|
|
119
|
+
if (data.method === "worker" && !((_b = app.worker) == null ? void 0 : _b.available)) {
|
|
119
120
|
ctx.throw(500, ctx.t("No worker thread", { ns: "worker-thread" }));
|
|
120
121
|
return next();
|
|
121
122
|
}
|
|
122
|
-
let useWorker = data.method === "worker" || data.method === "priority" && ((
|
|
123
|
-
const dumper = new import_dumper.Dumper(ctx.
|
|
124
|
-
const taskId = await dumper.getLockFile(ctx.
|
|
123
|
+
let useWorker = data.method === "worker" || data.method === "priority" && ((_c = app.worker) == null ? void 0 : _c.available);
|
|
124
|
+
const dumper = new import_dumper.Dumper(ctx.tego);
|
|
125
|
+
const taskId = await dumper.getLockFile(ctx.tego.name);
|
|
125
126
|
if (useWorker) {
|
|
126
127
|
app.worker.callPluginMethod({
|
|
127
128
|
plugin: import_server2.default,
|
|
128
129
|
method: "workerCreateBackUp",
|
|
129
130
|
params: {
|
|
130
131
|
dataTypes: data.dataTypes,
|
|
131
|
-
appName: ctx.
|
|
132
|
-
filename: taskId
|
|
132
|
+
appName: ctx.tego.name,
|
|
133
|
+
filename: taskId,
|
|
134
|
+
userId
|
|
133
135
|
},
|
|
134
136
|
// 目前限制方法并发为1
|
|
135
137
|
concurrency: 1
|
|
@@ -138,20 +140,21 @@ var backup_files_default = {
|
|
|
138
140
|
}).catch((error) => {
|
|
139
141
|
app.noticeManager.notify("backup", { level: "error", msg: error.message });
|
|
140
142
|
}).finally(() => {
|
|
141
|
-
dumper.cleanLockFile(taskId, ctx.
|
|
143
|
+
dumper.cleanLockFile(taskId, ctx.tego.name);
|
|
142
144
|
});
|
|
143
145
|
} else {
|
|
144
146
|
const plugin = app.pm.get(import_server2.default);
|
|
145
147
|
plugin.workerCreateBackUp({
|
|
146
148
|
dataTypes: data.dataTypes,
|
|
147
|
-
appName: ctx.
|
|
148
|
-
filename: taskId
|
|
149
|
+
appName: ctx.tego.name,
|
|
150
|
+
filename: taskId,
|
|
151
|
+
userId
|
|
149
152
|
}).then((res) => {
|
|
150
153
|
app.noticeManager.notify("backup", { level: "info", msg: ctx.t("Done", { ns: "backup" }) });
|
|
151
154
|
}).catch((error) => {
|
|
152
155
|
app.noticeManager.notify("backup", { level: "error", msg: error.message });
|
|
153
156
|
}).finally(() => {
|
|
154
|
-
dumper.cleanLockFile(taskId, ctx.
|
|
157
|
+
dumper.cleanLockFile(taskId, ctx.tego.name);
|
|
155
158
|
});
|
|
156
159
|
}
|
|
157
160
|
ctx.body = {
|
|
@@ -166,8 +169,8 @@ var backup_files_default = {
|
|
|
166
169
|
*/
|
|
167
170
|
async download(ctx, next) {
|
|
168
171
|
const { filterByTk } = ctx.action.params;
|
|
169
|
-
const dumper = new import_dumper.Dumper(ctx.
|
|
170
|
-
const filePath = dumper.backUpFilePath(filterByTk, ctx.
|
|
172
|
+
const dumper = new import_dumper.Dumper(ctx.tego);
|
|
173
|
+
const filePath = dumper.backUpFilePath(filterByTk, ctx.tego.name);
|
|
171
174
|
const fileState = await import_dumper.Dumper.getFileStatus(filePath);
|
|
172
175
|
if (fileState.status !== "ok") {
|
|
173
176
|
throw new Error(`Backup file ${filterByTk} not found`);
|
|
@@ -187,8 +190,8 @@ var backup_files_default = {
|
|
|
187
190
|
return import_node_path.default.resolve(tmpDir, key);
|
|
188
191
|
}
|
|
189
192
|
if (filterByTk) {
|
|
190
|
-
const dumper = new import_dumper.Dumper(ctx.
|
|
191
|
-
return dumper.backUpFilePath(filterByTk, ctx.
|
|
193
|
+
const dumper = new import_dumper.Dumper(ctx.tego);
|
|
194
|
+
return dumper.backUpFilePath(filterByTk, ctx.tego.name);
|
|
192
195
|
}
|
|
193
196
|
})();
|
|
194
197
|
if (!filePath) {
|
|
@@ -198,13 +201,13 @@ var backup_files_default = {
|
|
|
198
201
|
for (const dataType of dataTypes) {
|
|
199
202
|
args.push("-g", dataType);
|
|
200
203
|
}
|
|
201
|
-
await ctx.
|
|
204
|
+
await ctx.tego.runCommand(...args);
|
|
202
205
|
await next();
|
|
203
206
|
},
|
|
204
207
|
async destroy(ctx, next) {
|
|
205
208
|
const { filterByTk } = ctx.action.params;
|
|
206
|
-
const dumper = new import_dumper.Dumper(ctx.
|
|
207
|
-
const filePath = dumper.backUpFilePath(filterByTk, ctx.
|
|
209
|
+
const dumper = new import_dumper.Dumper(ctx.tego);
|
|
210
|
+
const filePath = dumper.backUpFilePath(filterByTk, ctx.tego.name);
|
|
208
211
|
await import_promises.default.unlink(filePath);
|
|
209
212
|
ctx.body = {
|
|
210
213
|
status: "ok"
|
|
@@ -214,7 +217,7 @@ var backup_files_default = {
|
|
|
214
217
|
async upload(ctx, next) {
|
|
215
218
|
const file = ctx.file;
|
|
216
219
|
const fileName = file.filename;
|
|
217
|
-
const restorer = new import_restorer.Restorer(ctx.
|
|
220
|
+
const restorer = new import_restorer.Restorer(ctx.tego, {
|
|
218
221
|
backUpFilePath: file.path
|
|
219
222
|
});
|
|
220
223
|
const restoreMeta = await restorer.parseBackupFile();
|
|
@@ -226,7 +229,7 @@ var backup_files_default = {
|
|
|
226
229
|
},
|
|
227
230
|
async dumpableCollections(ctx, next) {
|
|
228
231
|
ctx.withoutDataWrapping = true;
|
|
229
|
-
const dumper = new import_dumper.Dumper(ctx.
|
|
232
|
+
const dumper = new import_dumper.Dumper(ctx.tego);
|
|
230
233
|
ctx.body = await dumper.dumpableCollectionsGroupByGroup();
|
|
231
234
|
await next();
|
|
232
235
|
}
|
package/dist/server/server.d.ts
CHANGED
package/dist/server/server.js
CHANGED
|
@@ -57,6 +57,53 @@ const _PluginBackupRestoreServer = class _PluginBackupRestoreServer extends impo
|
|
|
57
57
|
}
|
|
58
58
|
async load() {
|
|
59
59
|
this.app.resourcer.define(import_backup_files.default);
|
|
60
|
+
const gateway = import_server.Gateway.getInstance();
|
|
61
|
+
const ws = gateway["wsServer"];
|
|
62
|
+
if (ws == null ? void 0 : ws.wss) {
|
|
63
|
+
const appName = this.app.name;
|
|
64
|
+
ws.wss.on(
|
|
65
|
+
"connection",
|
|
66
|
+
async (websocket) => {
|
|
67
|
+
websocket.on("message", async (data) => {
|
|
68
|
+
var _a, _b, _c;
|
|
69
|
+
if (data.toString() !== "ping") {
|
|
70
|
+
try {
|
|
71
|
+
const userMeg = JSON.parse(data.toString());
|
|
72
|
+
if (userMeg.type === "signIn") {
|
|
73
|
+
if (!((_a = userMeg.payload) == null ? void 0 : _a.token)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const analysis = await ((_c = (_b = this.app.authManager) == null ? void 0 : _b.jwt) == null ? void 0 : _c.verifyToken(userMeg.payload.token));
|
|
78
|
+
const userId = analysis.userId;
|
|
79
|
+
const client = ws.webSocketClients.get(websocket.id);
|
|
80
|
+
if (client) {
|
|
81
|
+
client.tags.forEach((tag2) => {
|
|
82
|
+
if (tag2.startsWith("app:")) {
|
|
83
|
+
client.tags.delete(tag2);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
const tag = `app:${appName}#${userId}`;
|
|
87
|
+
client.tags.add(tag);
|
|
88
|
+
this.app.logger.debug(`[Backup] WebSocket signIn: set tag ${tag} for connection ${websocket.id}`);
|
|
89
|
+
} else {
|
|
90
|
+
this.app.logger.warn(
|
|
91
|
+
`[Backup] WebSocket signIn: client not found for connection ${websocket.id}`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
this.app.logger.warn("[Backup] WebSocket signIn message connection error:", error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
} else {
|
|
105
|
+
this.app.logger.warn("[Backup] WebSocket server not available, backup progress will not be pushed via WebSocket");
|
|
106
|
+
}
|
|
60
107
|
this.app.on("afterStart", async (app) => {
|
|
61
108
|
const cronJobs = await app.db.getRepository(import_constants.COLLECTION_AUTOBACKUP).find({
|
|
62
109
|
filter: { enabled: true }
|
|
@@ -228,7 +275,8 @@ const _PluginBackupRestoreServer = class _PluginBackupRestoreServer extends impo
|
|
|
228
275
|
await new import_dumper.Dumper(this.app).runDumpTask({
|
|
229
276
|
groups: new Set(data.dataTypes),
|
|
230
277
|
appName: data.appName,
|
|
231
|
-
fileName: data.filename
|
|
278
|
+
fileName: data.filename,
|
|
279
|
+
userId: data.userId
|
|
232
280
|
});
|
|
233
281
|
}
|
|
234
282
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tachybase/module-backup",
|
|
3
3
|
"displayName": "App backup & restore",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.6.1",
|
|
5
5
|
"description": "Backup and restore applications for scenarios such as application replication, migration, and upgrades.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"System management"
|
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
"license": "Apache-2.0",
|
|
10
10
|
"main": "./dist/server/index.js",
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@ant-design/icons": "^
|
|
12
|
+
"@ant-design/icons": "^6.1.0",
|
|
13
13
|
"@hapi/topo": "^6.0.2",
|
|
14
|
-
"@tachybase/schema": "1.
|
|
15
|
-
"@tachybase/test": "1.
|
|
16
|
-
"@tego/client": "1.
|
|
17
|
-
"@tego/server": "1.
|
|
14
|
+
"@tachybase/schema": "1.6.0-alpha.9",
|
|
15
|
+
"@tachybase/test": "1.6.0-alpha.9",
|
|
16
|
+
"@tego/client": "1.6.0-alpha.9",
|
|
17
|
+
"@tego/server": "1.6.0-alpha.9",
|
|
18
18
|
"@types/archiver": "^5.3.4",
|
|
19
19
|
"@types/file-saver": "^2.0.7",
|
|
20
20
|
"@types/lodash": "^4.17.20",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"semver": "7.7.2",
|
|
35
35
|
"tar": "^7.4.3",
|
|
36
36
|
"yauzl": "^3.2.0",
|
|
37
|
-
"@tachybase/client": "1.
|
|
38
|
-
"@tachybase/module-worker-thread": "1.
|
|
37
|
+
"@tachybase/client": "1.6.1",
|
|
38
|
+
"@tachybase/module-worker-thread": "1.6.1"
|
|
39
39
|
},
|
|
40
40
|
"description.zh-CN": "备份和还原应用,可用于应用的复制、迁移、升级等场景。",
|
|
41
41
|
"displayName.zh-CN": "应用的备份与还原",
|