mirror of
https://github.com/MCSManager/MCSManager.git
synced 2025-01-12 14:54:34 +08:00
commit
58188a8c9d
@ -115,8 +115,8 @@ const submit = async () => {
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await initInstancesData();
|
||||
open.value = true;
|
||||
await initInstancesData();
|
||||
});
|
||||
|
||||
const handleQueryInstance = throttle(async () => {
|
||||
|
@ -1 +1,3 @@
|
||||
export const GLOBAL_INSTANCE_NAME = "__MCSM_GLOBAL_INSTANCE__";
|
||||
|
||||
export const GLOBAL_INSTANCE_UUID = "global0001";
|
||||
|
@ -342,6 +342,7 @@ export const useFileManager = (instanceId?: string, daemonId?: string) => {
|
||||
|
||||
await uploadFile({
|
||||
data: uploadFormData,
|
||||
timeout: Number.MAX_VALUE,
|
||||
url: `${parseForwardAddress(uploadCfg.value.addr, "http")}/upload/${
|
||||
uploadCfg.value.password
|
||||
}`,
|
||||
|
@ -167,10 +167,8 @@ export const uploadAddress = useDefineApi<
|
||||
export const uploadFile = useDefineApi<
|
||||
{
|
||||
data: FormData;
|
||||
url: string;
|
||||
onUploadProgress: Function;
|
||||
},
|
||||
{}
|
||||
any
|
||||
>({
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "multipart/form-data" }
|
||||
|
@ -191,10 +191,8 @@ export const uploadInstanceFile = useDefineApi<
|
||||
code: string;
|
||||
};
|
||||
data: FormData;
|
||||
url: string;
|
||||
onUploadProgress: Function;
|
||||
},
|
||||
{}
|
||||
any
|
||||
>({
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "multipart/form-data" }
|
||||
|
@ -22,3 +22,14 @@ export const resetLayoutConfig = useDefineApi<any, void>({
|
||||
url: "/api/overview/layout",
|
||||
method: "DELETE"
|
||||
});
|
||||
|
||||
export const uploadFile = useDefineApi<
|
||||
{
|
||||
data: FormData;
|
||||
},
|
||||
string
|
||||
>({
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
url: "/api/overview/upload_assets"
|
||||
});
|
||||
|
@ -4,7 +4,7 @@ import { onMounted } from "vue";
|
||||
import type { LayoutCard } from "@/types";
|
||||
import { userInfoApi } from "@/services/apis/index";
|
||||
import { useRouter } from "vue-router";
|
||||
import { INSTANCE_STATUS } from "@/types/const";
|
||||
import { INSTANCE_STATUS, INSTANCE_STATUS_CODE } from "@/types/const";
|
||||
|
||||
defineProps<{
|
||||
card: LayoutCard;
|
||||
@ -89,7 +89,7 @@ onMounted(() => {
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'operate'">
|
||||
<a-button @click="operate(record.daemonId, record.instanceUuid)">
|
||||
<a-button :disabled="record.status === INSTANCE_STATUS_CODE.UNKNOWN" @click="operate(record.daemonId, record.instanceUuid)">
|
||||
{{ t("TXT_CODE_5974bf24") }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
@ -11,7 +11,7 @@ import { useScreen } from "@/hooks/useScreen";
|
||||
import { arrayFilter } from "@/tools/array";
|
||||
import { useLayoutCardTools } from "@/hooks/useCardTools";
|
||||
import { throttle } from "lodash";
|
||||
import { filterFileName, getFileIcon } from "@/tools/fileManager";
|
||||
import { filterFileName, getFileExtName, getFileIcon } from "@/tools/fileManager";
|
||||
import { useFileManager } from "@/hooks/useFileManager";
|
||||
import FileEditor from "./dialogs/FileEditor.vue";
|
||||
import type { DataType } from "@/types/fileManager";
|
||||
@ -267,10 +267,7 @@ onMounted(() => {
|
||||
{{ disk }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<div
|
||||
class="file-breadcrumbs mb-20"
|
||||
:style="{ width: isPhone ? '100%' : 'calc(100% - 135px)' }"
|
||||
>
|
||||
<div class="file-breadcrumbs mb-20">
|
||||
<a-breadcrumb separator=">">
|
||||
<a-breadcrumb-item v-for="item in breadcrumbs" :key="item.path">
|
||||
<div class="file-breadcrumbs-item" @click="handleChangeDir(item.path)">
|
||||
@ -361,10 +358,18 @@ onMounted(() => {
|
||||
<a-menu-item key="6" @click="deleteFile(record.name)">
|
||||
{{ t("TXT_CODE_ecbd7449") }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="7" @click="unzipFile(record.name)">
|
||||
<a-menu-item
|
||||
v-if="record.type === 1 && getFileExtName(record.name) === 'zip'"
|
||||
key="7"
|
||||
@click="unzipFile(record.name)"
|
||||
>
|
||||
{{ t("TXT_CODE_a64f3007") }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="8" @click="downloadFile(record.name)">
|
||||
<a-menu-item
|
||||
v-if="record.type === 1"
|
||||
key="8"
|
||||
@click="downloadFile(record.name)"
|
||||
>
|
||||
{{ t("TXT_CODE_65b21404") }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
@ -511,6 +516,7 @@ onMounted(() => {
|
||||
.file-breadcrumbs {
|
||||
border: 1px solid var(--color-gray-5);
|
||||
border-radius: 6px;
|
||||
flex: 1;
|
||||
|
||||
.file-breadcrumbs-item {
|
||||
padding: 8px;
|
||||
|
@ -22,6 +22,8 @@ import { message } from "ant-design-vue";
|
||||
import { useAppRouters } from "@/hooks/useAppRouters";
|
||||
import { useLayoutCardTools } from "@/hooks/useCardTools";
|
||||
import type { LayoutCard } from "@/types";
|
||||
import { arrayFilter } from "@/tools/array";
|
||||
import { GLOBAL_INSTANCE_UUID } from "@/config/const";
|
||||
|
||||
const props = defineProps<{
|
||||
item?: ComputedNodeInfo;
|
||||
@ -81,62 +83,67 @@ const detailList = (node: ComputedNodeInfo) => {
|
||||
];
|
||||
};
|
||||
|
||||
const nodeOperations = [
|
||||
{
|
||||
title: t("TXT_CODE_ae533703"),
|
||||
icon: FolderOpenOutlined,
|
||||
click: (item: ComputedNodeInfo) => {
|
||||
const daemonId = item.uuid;
|
||||
const instanceId = "global0001";
|
||||
toPage({
|
||||
path: "/instances/terminal/files",
|
||||
query: {
|
||||
daemonId,
|
||||
instanceId
|
||||
}
|
||||
});
|
||||
const nodeOperations = computed(() =>
|
||||
arrayFilter([
|
||||
{
|
||||
title: t("TXT_CODE_ae533703"),
|
||||
icon: FolderOpenOutlined,
|
||||
click: (item: ComputedNodeInfo) => {
|
||||
const daemonId = item.uuid;
|
||||
const instanceId = GLOBAL_INSTANCE_UUID;
|
||||
toPage({
|
||||
path: "/instances/terminal/files",
|
||||
query: {
|
||||
daemonId,
|
||||
instanceId
|
||||
}
|
||||
});
|
||||
},
|
||||
condition: () => item.value!.available
|
||||
},
|
||||
{
|
||||
title: t("TXT_CODE_524e3036"),
|
||||
icon: CodeOutlined,
|
||||
click: (item: ComputedNodeInfo) => {
|
||||
const daemonId = item.uuid;
|
||||
const instanceId = GLOBAL_INSTANCE_UUID;
|
||||
toPage({
|
||||
path: "/instances/terminal",
|
||||
query: {
|
||||
daemonId,
|
||||
instanceId
|
||||
}
|
||||
});
|
||||
},
|
||||
condition: () => item.value!.available
|
||||
},
|
||||
{
|
||||
title: t("TXT_CODE_e6c30866"),
|
||||
icon: BlockOutlined,
|
||||
click: (item: ComputedNodeInfo) => {
|
||||
const daemonId = item.uuid;
|
||||
toPage({
|
||||
path: "/node/image",
|
||||
query: {
|
||||
daemonId
|
||||
}
|
||||
});
|
||||
},
|
||||
condition: () => item.value!.available
|
||||
},
|
||||
{
|
||||
title: t("TXT_CODE_b5c7b82d"),
|
||||
icon: SettingOutlined,
|
||||
click: (node: ComputedNodeInfo) => {
|
||||
editDialog.value.uuid = node.uuid;
|
||||
editDialog.value.data.ip = node.ip;
|
||||
editDialog.value.data.port = node.port;
|
||||
editDialog.value.data.remarks = node.remarks;
|
||||
editDialog.value.showEdit();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t("TXT_CODE_524e3036"),
|
||||
icon: CodeOutlined,
|
||||
click: (item: ComputedNodeInfo) => {
|
||||
const daemonId = item.uuid;
|
||||
const instanceId = "global0001";
|
||||
toPage({
|
||||
path: "/instances/terminal",
|
||||
query: {
|
||||
daemonId,
|
||||
instanceId
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t("TXT_CODE_e6c30866"),
|
||||
icon: BlockOutlined,
|
||||
click: (item: ComputedNodeInfo) => {
|
||||
const daemonId = item.uuid;
|
||||
toPage({
|
||||
path: "/node/image",
|
||||
query: {
|
||||
daemonId
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t("TXT_CODE_b5c7b82d"),
|
||||
icon: SettingOutlined,
|
||||
click: (node: ComputedNodeInfo) => {
|
||||
editDialog.value.uuid = node.uuid;
|
||||
editDialog.value.data.ip = node.ip;
|
||||
editDialog.value.data.port = node.port;
|
||||
editDialog.value.data.remarks = node.remarks;
|
||||
editDialog.value.showEdit();
|
||||
}
|
||||
}
|
||||
];
|
||||
])
|
||||
);
|
||||
|
||||
const deleteNode = async () => {
|
||||
const { execute } = deleteNodeApi();
|
||||
|
@ -1,33 +1,70 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { $t as t } from "@/lang/i18n";
|
||||
import { useAppToolsStore } from "@/stores/useAppToolsStore";
|
||||
import { t } from "@/lang/i18n";
|
||||
import { useLayoutContainerStore } from "@/stores/useLayoutContainerStore";
|
||||
import CardPanel from "@/components/CardPanel.vue";
|
||||
import type { LayoutCard } from "@/types/index";
|
||||
import { Empty } from "ant-design-vue";
|
||||
import { Empty, message, type UploadProps } from "ant-design-vue";
|
||||
import { UploadOutlined } from "@ant-design/icons-vue";
|
||||
import { useLayoutCardTools } from "@/hooks/useCardTools";
|
||||
import { uploadFile } from "@/services/apis/layout";
|
||||
import { useAppStateStore } from "@/stores/useAppStateStore";
|
||||
|
||||
const props = defineProps<{
|
||||
card: LayoutCard;
|
||||
}>();
|
||||
|
||||
const { getMetaValue, setMetaValue } = useLayoutCardTools(props.card);
|
||||
|
||||
const { containerState } = useLayoutContainerStore();
|
||||
const { isAdmin } = useAppStateStore();
|
||||
const imgSrc = ref(getMetaValue("image", ""));
|
||||
const { openInputDialog } = useAppToolsStore();
|
||||
const open = ref(false);
|
||||
const activeKey = ref("upload");
|
||||
const percentComplete = ref(0);
|
||||
const uploadControl = new AbortController();
|
||||
|
||||
const { state, execute } = uploadFile();
|
||||
const beforeUpload: UploadProps["beforeUpload"] = async (file) => {
|
||||
const uploadFormData = new FormData();
|
||||
uploadFormData.append("file", file);
|
||||
await execute({
|
||||
data: uploadFormData,
|
||||
timeout: Number.MAX_VALUE,
|
||||
signal: uploadControl.signal,
|
||||
onUploadProgress: (progressEvent: any) => {
|
||||
percentComplete.value = Math.round((progressEvent.loaded * 100) / progressEvent.total);
|
||||
}
|
||||
});
|
||||
if (state.value) {
|
||||
imgSrc.value = `${window.location.origin}/upload_files/${state.value}`;
|
||||
setMetaValue("image", imgSrc.value);
|
||||
percentComplete.value = 0;
|
||||
message.success(t("TXT_CODE_773f36a0"));
|
||||
open.value = false;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
setMetaValue("image", imgSrc.value);
|
||||
open.value = false;
|
||||
};
|
||||
|
||||
const editImgSrc = async () => {
|
||||
try {
|
||||
imgSrc.value = (await openInputDialog(t("TXT_CODE_c8a51b2e"))) as string;
|
||||
setMetaValue("image", imgSrc.value);
|
||||
} catch (error) {}
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
if (percentComplete.value !== 0) {
|
||||
percentComplete.value = 0;
|
||||
uploadControl.abort();
|
||||
}
|
||||
open.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="width: 100%; position: relative">
|
||||
<div style="width: 100%; height: 100%; position: relative">
|
||||
<div v-if="imgSrc !== '' && containerState.isDesignMode" class="mask">
|
||||
<a-button type="primary" @click="editImgSrc()">
|
||||
{{ t("TXT_CODE_fd13f431") }}
|
||||
@ -40,13 +77,67 @@ const editImgSrc = async () => {
|
||||
<template #description>
|
||||
<span>{{ t("TXT_CODE_635d051") }}</span>
|
||||
</template>
|
||||
<a-button type="primary" @click="editImgSrc()">
|
||||
<a-button
|
||||
:disabled="!containerState.isDesignMode || !isAdmin"
|
||||
type="primary"
|
||||
@click="editImgSrc()"
|
||||
>
|
||||
{{ t("TXT_CODE_589e091c") }}
|
||||
</a-button>
|
||||
</a-empty>
|
||||
</template>
|
||||
</CardPanel>
|
||||
</div>
|
||||
<a-modal v-model:open="open" :title="null" :closable="false" :destroy-on-close="true">
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="upload" :tab="t('TXT_CODE_e00c858c')">
|
||||
<a-progress
|
||||
v-if="percentComplete > 0"
|
||||
:stroke-color="{
|
||||
'0%': '#49b3ff',
|
||||
'100%': '#25f5b9'
|
||||
}"
|
||||
:percent="percentComplete"
|
||||
class="mb-20"
|
||||
/>
|
||||
|
||||
<a-upload
|
||||
:max-count="1"
|
||||
:disabled="percentComplete > 0"
|
||||
:show-upload-list="false"
|
||||
:before-upload="beforeUpload"
|
||||
>
|
||||
<a-button type="primary" :loading="percentComplete > 0">
|
||||
<upload-outlined v-if="percentComplete === 0" />
|
||||
{{
|
||||
percentComplete > 0
|
||||
? t("TXT_CODE_b625dbf0") + percentComplete + "%"
|
||||
: t("TXT_CODE_e00c858c")
|
||||
}}
|
||||
</a-button>
|
||||
</a-upload>
|
||||
<a-typography class="mt-20">
|
||||
<a-typography-title :level="5">{{ t("温馨提示") }}</a-typography-title>
|
||||
<a-typography-paragraph>
|
||||
<ol>
|
||||
<li>{{ t("上传后将自动保存") }}</li>
|
||||
<li>{{ t("点击关闭按钮可取消上传") }}</li>
|
||||
<li>{{ t("你可以通过 “重置布局” 来清空你上传的所有文件") }}</li>
|
||||
</ol>
|
||||
</a-typography-paragraph>
|
||||
</a-typography>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="url" :tab="t('网络URL')" force-render>
|
||||
<a-input v-model:value.lazy.trim="imgSrc" autofocus :placeholder="t('TXT_CODE_c8a51b2e')" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<template #footer>
|
||||
<a-button @click="close()">{{ t("关闭") }}</a-button>
|
||||
<a-button v-if="activeKey === 'url'" type="primary" @click="save">
|
||||
{{ t("保存") }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -152,6 +152,7 @@ const selectedFile = async () => {
|
||||
unzip: isImportMode ? UNZIP.ON : UNZIP.OFF,
|
||||
code: zipCode.value
|
||||
},
|
||||
timeout: Number.MAX_VALUE,
|
||||
data: uploadFormData,
|
||||
url: `${parseForwardAddress(cfg.value.addr, "http")}/upload/${cfg.value.password}`,
|
||||
onUploadProgress: (progressEvent: any) => {
|
||||
|
@ -14,7 +14,10 @@ export default defineConfig({
|
||||
target: "http://localhost:23333",
|
||||
changeOrigin: true,
|
||||
ws: true
|
||||
// rewrite: (path) => path.replace(/^\/api/, "")
|
||||
},
|
||||
"/upload_files": {
|
||||
target: "http://localhost:23333",
|
||||
changeOrigin: true
|
||||
},
|
||||
"/socket.io": {
|
||||
target: "ws://localhost:23333",
|
||||
|
@ -110,8 +110,6 @@ _ / / / / /___ ____/ /_ / / / / /_/ /_ / / / /_/ /_ /_/ // __/ /
|
||||
await SystemUser.initialize();
|
||||
await SystemRemoteService.initialize();
|
||||
|
||||
const BASE_PATH = __dirname;
|
||||
|
||||
const app = new Koa();
|
||||
|
||||
// Listen for Koa errors
|
||||
@ -169,7 +167,7 @@ _ / / / / /___ ____/ /_ / / / / /_/ /_ / / / /_/ /_ /_/ // __/ /
|
||||
const koaStaticOptions = {
|
||||
maxAge: 10 * 24 * 60 * 60 //Cache for ten days. Changed files will not be load from cache.
|
||||
};
|
||||
app.use(koaStatic(path.join(BASE_PATH, "public"), koaStaticOptions));
|
||||
app.use(koaStatic(path.join(process.cwd(), "public"), koaStaticOptions));
|
||||
|
||||
// Websocket routing (useless for now)
|
||||
// import SocketService from "./app/service/socket_service";
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
setFrontendLayoutConfig
|
||||
} from "../../service/frontend_layout";
|
||||
import { ROLE } from "../../entity/user";
|
||||
import { SAVE_DIR_PATH } from "../../service/frontend_layout";
|
||||
|
||||
const router = new Router({ prefix: "/overview" });
|
||||
|
||||
@ -102,7 +103,7 @@ router.post("/upload_assets", permission({ level: ROLE.ADMIN }), async (ctx) =>
|
||||
if (!tmpFiles.path || !fs.existsSync(tmpFiles.path)) throw new Error($t("TXT_CODE_1a499109"));
|
||||
const tmpFile = tmpFiles;
|
||||
const newFileName = v4() + path.extname(tmpFile.name);
|
||||
const saveDirPath = path.join(process.cwd(), "public/upload_files/");
|
||||
const saveDirPath = path.join(process.cwd(), SAVE_DIR_PATH);
|
||||
if (!fs.existsSync(saveDirPath)) fs.mkdirsSync(saveDirPath);
|
||||
await fs.move(tmpFile.path, path.join(saveDirPath, newFileName));
|
||||
ctx.body = newFileName;
|
||||
|
@ -3,9 +3,13 @@ import { IPageLayoutConfig } from "../../../../common/global";
|
||||
import { $t as t } from "../i18n";
|
||||
import storage from "../common/system_storage";
|
||||
import { GlobalVariable } from "common";
|
||||
import path from "path";
|
||||
import fs from "fs-extra";
|
||||
|
||||
const LAYOUT_CONFIG_NAME = "layout.json";
|
||||
|
||||
export const SAVE_DIR_PATH = "public/upload_files/";
|
||||
|
||||
function getRandomId() {
|
||||
return v4();
|
||||
}
|
||||
@ -40,6 +44,10 @@ export function setFrontendLayoutConfig(config: IPageLayoutConfig[]) {
|
||||
|
||||
export function resetFrontendLayoutConfig() {
|
||||
storage.deleteFile(LAYOUT_CONFIG_NAME);
|
||||
const filesDir = path.join(process.cwd(), SAVE_DIR_PATH);
|
||||
for (const fileName of fs.readdirSync(filesDir)) {
|
||||
fs.remove(path.join(filesDir, fileName), () => {});
|
||||
}
|
||||
}
|
||||
|
||||
export enum LayoutCardHeight {
|
||||
|
Loading…
Reference in New Issue
Block a user