diff --git a/dashboard/src/translation/en.json b/dashboard/src/translation/en.json
index 7f1c685be..7dcb56c8d 100644
--- a/dashboard/src/translation/en.json
+++ b/dashboard/src/translation/en.json
@@ -1143,7 +1143,8 @@
"copy": "Copy",
"clear": "Clear",
"pasteInfo": "For Paste use Ctrl+v"
- }
+ },
+ "uploadTo": "Upload to {{ path }}"
},
"filemanager": {
"title": "File Manager",
@@ -1592,7 +1593,7 @@
"packageVersion": "Package Version",
"lastUpdated": "Last Updated",
"checkForUpdatesAction": "Check for Updates",
- "customAppUpdateInfo": "Auto-update is not available for custom apps",
+ "customAppUpdateInfo": "Auto-update is not available for custom apps.",
"updateAvailableAction": "Update Available",
"repository": "Package Repository",
"installedAt": "Installed At"
diff --git a/frontend/src/components/Terminal.vue b/frontend/src/components/Terminal.vue
index e0410a20f..91acd5c33 100644
--- a/frontend/src/components/Terminal.vue
+++ b/frontend/src/components/Terminal.vue
@@ -24,7 +24,7 @@
-
+
@@ -58,6 +58,7 @@ import { AttachAddon } from '@xterm/addon-attach';
import { FitAddon } from '@xterm/addon-fit';
import { create } from '../models/AppModel.js';
+import { createDirectoryModel } from '../models/DirectoryModel.js';
const API_ORIGIN = import.meta.env.VITE_API_ORIGIN ? 'https://' + import.meta.env.VITE_API_ORIGIN : window.location.origin;
@@ -76,6 +77,7 @@ export default {
accessToken: localStorage.token,
apiOrigin: API_ORIGIN || '',
appModel: null,
+ directoryModel: null,
fatalError: false,
busyRestart: false,
connected: false,
@@ -135,13 +137,10 @@ export default {
});
},
onUpload() {
- this.$refs.fileUploader.onUploadFile('/tmp');
+ this.$refs.fileUploader.onUploadFile('/');
},
async uploadHandler(targetDir, file, progressHandler) {
- await superagent.post(`${this.apiOrigin}/api/v1/apps/${this.id}/upload`)
- .query({ access_token: this.accessToken, file: `${targetDir}/${file.name}` })
- .attach('file', file)
- .on('progress', progressHandler);
+ await this.directoryModel.upload(targetDir, file, progressHandler);
},
usesAddon(addon) {
return !!Object.keys(this.addons).find(function (a) { return a === addon; });
@@ -265,6 +264,7 @@ export default {
this.name = id;
this.appModel = create(this.apiOrigin, this.accessToken, this.id);
+ this.directoryModel = createDirectoryModel(this.apiOrigin, this.accessToken, `apps/${id}`);
try {
const app = await this.appModel.get();
diff --git a/frontend/src/filemanager.js b/frontend/src/filemanager.js
index a0ce00a0e..485d52bde 100644
--- a/frontend/src/filemanager.js
+++ b/frontend/src/filemanager.js
@@ -27,9 +27,18 @@ const router = createRouter({
const translations = {};
const i18n = createI18n({
- locale: 'en', // set locale
- fallbackLocale: 'en', // set fallback locale
- messages: translations
+ locale: 'en', // set locale
+ fallbackLocale: 'en', // set fallback locale
+ messages: translations,
+ // will replace our double {{}} to vue-i18n single brackets
+ messageResolver: (keys, key) => {
+ let message = key.split('.').reduce((o, k) => o && o[k] || null, keys);
+
+ // fallback tr key
+ if (message === null) message = key;
+
+ return message.replaceAll('{{', '{').replaceAll('}}', '}');
+ }
});
// https://vue-i18n.intlify.dev/guide/advanced/lazy.html
diff --git a/frontend/src/logs.js b/frontend/src/logs.js
index 2dc830d12..56e59958a 100644
--- a/frontend/src/logs.js
+++ b/frontend/src/logs.js
@@ -11,9 +11,18 @@ import LogsViewer from './components/LogsViewer.vue';
const translations = {};
const i18n = createI18n({
- locale: 'en', // set locale
- fallbackLocale: 'en', // set fallback locale
- messages: translations
+ locale: 'en', // set locale
+ fallbackLocale: 'en', // set fallback locale
+ messages: translations,
+ // will replace our double {{}} to vue-i18n single brackets
+ messageResolver: (keys, key) => {
+ let message = key.split('.').reduce((o, k) => o && o[k] || null, keys);
+
+ // fallback tr key
+ if (message === null) message = key;
+
+ return message.replaceAll('{{', '{').replaceAll('}}', '}');
+ }
});
// https://vue-i18n.intlify.dev/guide/advanced/lazy.html
diff --git a/frontend/src/terminal.js b/frontend/src/terminal.js
index 3416b0d3c..c32c1ef25 100644
--- a/frontend/src/terminal.js
+++ b/frontend/src/terminal.js
@@ -11,9 +11,18 @@ import Terminal from './components/Terminal.vue';
const translations = {};
const i18n = createI18n({
- locale: 'en', // set locale
- fallbackLocale: 'en', // set fallback locale
- messages: translations
+ locale: 'en', // set locale
+ fallbackLocale: 'en', // set fallback locale
+ messages: translations,
+ // will replace our double {{}} to vue-i18n single brackets
+ messageResolver: (keys, key) => {
+ let message = key.split('.').reduce((o, k) => o && o[k] || null, keys);
+
+ // fallback tr key
+ if (message === null) message = key;
+
+ return message.replaceAll('{{', '{').replaceAll('}}', '}');
+ }
});
// https://vue-i18n.intlify.dev/guide/advanced/lazy.html
diff --git a/src/apps.js b/src/apps.js
index bca61124e..b1272bcd8 100644
--- a/src/apps.js
+++ b/src/apps.js
@@ -93,7 +93,6 @@ exports = module.exports = {
listEventlog,
downloadFile,
- uploadFile,
writeConfig,
loadConfig,
@@ -2888,32 +2887,6 @@ async function downloadFile(app, filePath) {
return { stream: stdoutStream, filename, size };
}
-async function uploadFile(app, sourceFilePath, destFilePath) {
- assert.strictEqual(typeof app, 'object');
- assert.strictEqual(typeof sourceFilePath, 'string');
- assert.strictEqual(typeof destFilePath, 'string');
-
- // the built-in bash printf understands "%q" but not /usr/bin/printf.
- // ' gets replaced with '\'' . the first closes the quote and last one starts a new one
- const escapedDestFilePath = await shell.exec('uploadFile', `printf %q '${destFilePath.replace(/'/g, '\'\\\'\'')}'`, { shell: '/bin/bash' });
- debug(`uploadFile: ${sourceFilePath} -> ${escapedDestFilePath}`);
-
- const execId = await createExec(app, { cmd: [ 'bash', '-c', `cat - > ${escapedDestFilePath}` ], tty: false });
- const destStream = await startExec(app, execId, { tty: false });
-
- return new Promise((resolve, reject) => {
- const done = once(error => reject(new BoxError(BoxError.FS_ERROR, error.message)));
-
- const sourceStream = fs.createReadStream(sourceFilePath);
- sourceStream.on('error', done);
- destStream.on('error', done);
-
- destStream.on('finish', resolve);
-
- sourceStream.pipe(destStream);
- });
-}
-
async function writeConfig(app) {
assert.strictEqual(typeof app, 'object');
diff --git a/src/routes/apps.js b/src/routes/apps.js
index 9b07b2de9..2c5430a4f 100644
--- a/src/routes/apps.js
+++ b/src/routes/apps.js
@@ -57,7 +57,6 @@ exports = module.exports = {
clone,
- uploadFile,
downloadFile,
updateBackup,
@@ -930,21 +929,6 @@ async function downloadBackup(req, res, next) {
result.stream.pipe(res);
}
-async function uploadFile(req, res, next) {
- assert.strictEqual(typeof req.app, 'object');
-
- if (typeof req.query.file !== 'string' || !req.query.file) return next(new HttpError(400, 'file query argument must be provided'));
- if (!req.files.file) return next(new HttpError(400, 'file must be provided as multipart'));
-
- req.clearTimeout();
-
- const [error] = await safe(apps.uploadFile(req.app, req.files.file.path, req.query.file));
- safe.fs.unlinkSync(req.files.file.path);
-
- if (error) return next(BoxError.toHttpError(error));
- next(new HttpSuccess(202, {}));
-}
-
async function downloadFile(req, res, next) {
assert.strictEqual(typeof req.app, 'object');
diff --git a/src/server.js b/src/server.js
index a85093188..ba2befece 100644
--- a/src/server.js
+++ b/src/server.js
@@ -283,7 +283,6 @@ async function initializeExpressSync() {
router.get ('/api/v1/apps/:id/graphs', token, routes.apps.load, authorizeOperator, routes.apps.getGraphs);
router.post('/api/v1/apps/:id/clone', json, token, routes.apps.load, authorizeAdmin, routes.apps.clone);
router.get ('/api/v1/apps/:id/download', token, routes.apps.load, authorizeOperator, routes.apps.downloadFile);
- router.post('/api/v1/apps/:id/upload', json, token, multipart, routes.apps.load, authorizeOperator, routes.apps.uploadFile);
router.use ('/api/v1/apps/:id/files/*', token, routes.apps.load, authorizeOperator, routes.filemanager.proxy('app'));
router.post('/api/v1/apps/:id/exec', json, token, routes.apps.load, authorizeOperator, routes.apps.createExec);
router.get ('/api/v1/apps/:id/exec/:execId/start', token, routes.apps.load, authorizeOperator, routes.apps.startExec);