Merge branch 'next' into abandon

This commit is contained in:
Lazy 2023-11-07 20:46:57 +08:00
commit 6541549732
18 changed files with 689 additions and 640 deletions

36
common/global.d.ts vendored
View File

@ -131,3 +131,39 @@ export interface IPanelOverviewResponse {
};
remote: IPanelOverviewRemoteResponse[];
}
export interface IJsonData {
[key: string]: any;
}
export interface IMapData<T> {
[key: string]: T;
}
export interface IPageLayoutConfig {
page: string;
items: ILayoutCard[];
}
export interface ILayoutCardParams {
field: string;
label: string;
type: "string" | "number" | "boolean" | "instanceId";
}
export interface ILayoutCard {
id: string;
type: string;
title: string;
width: number;
height: string;
meta: IJsonData;
disableAdd?: boolean;
onlyPath?: string[];
params?: ILayoutCardParams[];
followId?: string;
description?: string;
allowedPages?: Array<string> | null;
line?: number;
disableDelete?: boolean;
}

View File

@ -1,4 +1,3 @@
import { $t } from "../i18n";
import path from "path";
import fs from "fs-extra";
@ -7,10 +6,8 @@ interface IClassz {
}
class StorageSubsystem {
public static readonly STIRAGE_DATA_PATH = path.normalize(path.join(process.cwd(), "data"));
public static readonly STIRAGE_INDEX_PATH = path.normalize(
path.join(process.cwd(), "data", "index")
);
public static readonly DATA_PATH = path.normalize(path.join(process.cwd(), "data"));
public static readonly INDEX_PATH = path.normalize(path.join(process.cwd(), "data", "index"));
private checkFileName(name: string) {
const blackList = ["\\", "/", ".."];
@ -20,21 +17,18 @@ class StorageSubsystem {
return true;
}
/**
* Stored in local file based on class definition and identifier
*/
// Stored in local file based on class definition and identifier
public store(category: string, uuid: string, object: any) {
const dirPath = path.join(StorageSubsystem.STIRAGE_DATA_PATH, category);
const dirPath = path.join(StorageSubsystem.DATA_PATH, category);
if (!fs.existsSync(dirPath)) fs.mkdirsSync(dirPath);
if (!this.checkFileName(uuid))
throw new Error($t("TXT_CODE_common.uuidIrregular", { uuid: uuid }));
throw new Error(`UUID ${uuid} does not conform to specification`);
const filePath = path.join(dirPath, `${uuid}.json`);
const data = JSON.stringify(object, null, 4);
fs.writeFileSync(filePath, data, { encoding: "utf-8" });
}
// deep copy of the primitive type with the copy target as the prototype
// target copy target object copy source
protected defineAttr(target: any, object: any): any {
for (const v of Object.keys(target)) {
const objectValue = object[v];
@ -56,10 +50,10 @@ class StorageSubsystem {
* Instantiate an object based on the class definition and identifier
*/
public load(category: string, classz: any, uuid: string) {
const dirPath = path.join(StorageSubsystem.STIRAGE_DATA_PATH, category);
const dirPath = path.join(StorageSubsystem.DATA_PATH, category);
if (!fs.existsSync(dirPath)) fs.mkdirsSync(dirPath);
if (!this.checkFileName(uuid))
throw new Error($t("TXT_CODE_common.uuidIrregular", { uuid: uuid }));
throw new Error(`UUID ${uuid} does not conform to specification`);
const filePath = path.join(dirPath, `${uuid}.json`);
if (!fs.existsSync(filePath)) return null;
const data = fs.readFileSync(filePath, { encoding: "utf-8" });
@ -76,7 +70,7 @@ class StorageSubsystem {
* Return all identifiers related to this class through the class definition
*/
public list(category: string) {
const dirPath = path.join(StorageSubsystem.STIRAGE_DATA_PATH, category);
const dirPath = path.join(StorageSubsystem.DATA_PATH, category);
if (!fs.existsSync(dirPath)) fs.mkdirsSync(dirPath);
const files = fs.readdirSync(dirPath);
const result = new Array<string>();
@ -90,7 +84,7 @@ class StorageSubsystem {
* Delete an identifier instance of the specified type through the class definition
*/
public delete(category: string, uuid: string) {
const filePath = path.join(StorageSubsystem.STIRAGE_DATA_PATH, category, `${uuid}.json`);
const filePath = path.join(StorageSubsystem.DATA_PATH, category, `${uuid}.json`);
if (!fs.existsSync(filePath)) return;
fs.removeSync(filePath);
}

View File

@ -1 +0,0 @@
# MCSManager 10.0 网页前端

View File

@ -1,7 +1,4 @@
/* eslint-disable no-unused-vars */
import { $t as t } from "@/lang/i18n";
import { getRandomId } from "@/tools/randId";
import type { JsonData, LayoutCard } from "@/types";
import type { IPageLayoutConfig } from "../../../common/global";
export enum LayoutCardHeight {
MINI = "100px",
@ -12,12 +9,9 @@ export enum LayoutCardHeight {
AUTO = "unset"
}
export interface PageLayoutConfig {
page: string;
items: LayoutCard[];
}
export let ORIGIN_LAYOUT_CONFIG: IPageLayoutConfig[] = [];
export function setAllLayoutConfig(cfg: PageLayoutConfig[]) {
export function setAllLayoutConfig(cfg: IPageLayoutConfig[]) {
console.debug("设置布局配置:", cfg);
ORIGIN_LAYOUT_CONFIG = cfg;
}
@ -25,457 +19,3 @@ export function setAllLayoutConfig(cfg: PageLayoutConfig[]) {
export function getAllLayoutConfig() {
return ORIGIN_LAYOUT_CONFIG;
}
// 在 Web 端没有返回用户自定义布局时,使用默认的布局。
// 这里是所有不同页面的默认的布局配置
export let ORIGIN_LAYOUT_CONFIG: PageLayoutConfig[] = [
{
page: "/",
items: [
{
id: getRandomId(),
type: "StatusBlock",
title: t("TXT_CODE_e627e546"),
meta: {
type: "node"
},
width: 3,
description: t("TXT_CODE_55ade942"),
height: LayoutCardHeight.SMALL
},
{
id: getRandomId(),
type: "StatusBlock",
title: t("TXT_CODE_88e9361a"),
meta: {
type: "instance"
},
width: 3,
description: t("TXT_CODE_55ade942"),
height: LayoutCardHeight.SMALL
},
{
id: getRandomId(),
type: "StatusBlock",
title: t("TXT_CODE_db64faf6"),
meta: {
type: "users"
},
width: 3,
description: t("TXT_CODE_55ade942"),
height: LayoutCardHeight.SMALL
},
{
id: getRandomId(),
type: "StatusBlock",
title: t("TXT_CODE_66056676"),
meta: {
type: "system"
},
width: 3,
description: t("TXT_CODE_55ade942"),
height: LayoutCardHeight.SMALL
},
{
id: getRandomId(),
meta: {},
type: "DataOverview",
title: t("TXT_CODE_721157a3"),
width: 9,
description: t("TXT_CODE_55ade942"),
height: LayoutCardHeight.MEDIUM
},
{
id: getRandomId(),
meta: {},
type: "QuickStart",
title: t("TXT_CODE_2799a1dd"),
width: 3,
description: t("TXT_CODE_55ade942"),
height: LayoutCardHeight.MEDIUM
},
{
id: getRandomId(),
meta: {},
type: "RequestChart",
title: t("TXT_CODE_a4037a98"),
width: 6,
description: t("TXT_CODE_55ade942"),
height: LayoutCardHeight.SMALL
},
{
id: getRandomId(),
meta: {},
type: "InstanceChart",
title: t("TXT_CODE_d6d9c42c"),
width: 6,
description: t("TXT_CODE_55ade942"),
height: LayoutCardHeight.SMALL
},
{
id: getRandomId(),
meta: {},
type: "NodeOverview",
title: t("TXT_CODE_bfb50126"),
width: 12,
description: t("TXT_CODE_55ade942"),
height: LayoutCardHeight.MEDIUM
}
]
},
{
page: "/instances",
items: [
{
id: getRandomId(),
meta: {},
type: "InstanceList",
title: t("TXT_CODE_e21473bc"),
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/instances/terminal",
items: [
{
id: getRandomId(),
meta: {
viewType: "inner"
},
type: "Terminal",
title: t("TXT_CODE_4ccdd3a0"),
width: 12,
height: LayoutCardHeight.BIG,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "InstanceBaseInfo",
title: t("TXT_CODE_eadb4f60"),
width: 4,
height: LayoutCardHeight.SMALL
},
{
id: getRandomId(),
meta: {},
type: "InstanceManagerBtns",
title: t("TXT_CODE_efd37c48"),
width: 8,
height: LayoutCardHeight.SMALL,
disableDelete: true
}
]
},
{
page: "/instances/terminal/files",
items: [
{
id: getRandomId(),
meta: {},
type: "InstanceFileManager",
title: t("文件管理"),
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/instances/terminal/serverConfig",
items: [
{
id: getRandomId(),
meta: {},
type: "InstanceServerConfigOverview",
title: t("服务端配置文件"),
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/instances/terminal/serverConfig/fileEdit",
items: [
{
id: getRandomId(),
meta: {},
type: "InstanceServerConfigFile",
title: t("编辑服务端配置文件"),
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/users",
items: [
{
id: getRandomId(),
meta: {},
type: "UserList",
title: t("用户列表"),
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/users/config",
items: [
{
id: getRandomId(),
meta: {},
type: "UserAccessSettings",
title: t("用户权限设定"),
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/settings",
items: [
{
id: getRandomId(),
meta: {},
type: "Settings",
title: t("系统设置"),
width: 8,
height: LayoutCardHeight.MEDIUM,
disableDelete: true
}
]
},
{
page: "/node",
items: [
{
id: getRandomId(),
meta: {},
type: "NodeList",
title: t("TXT_CODE_20509fa0"),
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/node/image",
items: [
{
id: getRandomId(),
meta: {},
type: "ImageManager",
title: t("镜像管理"),
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/node/image/new",
items: [
{
id: getRandomId(),
meta: {},
type: "NewImage",
title: t("创建镜像"),
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/quickstart",
items: [
{
id: getRandomId(),
meta: {},
type: "QuickStartFlow",
title: t("TXT_CODE_9b99b72e"),
width: 8,
height: LayoutCardHeight.AUTO
}
]
},
{
page: "/quickstart/minecraft",
items: [
{
id: getRandomId(),
meta: {},
type: "McPreset",
title: "",
width: 12,
height: LayoutCardHeight.AUTO
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/customer",
items: [
{
id: getRandomId(),
type: "UserStatusBlock",
title: t("实例总计"),
meta: {
type: "instance_all"
},
width: 3,
height: LayoutCardHeight.SMALL,
disableDelete: true
},
{
id: getRandomId(),
type: "UserStatusBlock",
title: t("正在运行"),
meta: {
type: "instance_running"
},
width: 3,
height: LayoutCardHeight.SMALL,
disableDelete: true
},
{
id: getRandomId(),
type: "UserStatusBlock",
title: t("未运行"),
meta: {
type: "instance_stop"
},
width: 3,
height: LayoutCardHeight.SMALL,
disableDelete: true
},
{
id: getRandomId(),
type: "UserStatusBlock",
title: t("维护中"),
meta: {
type: "instance_error"
},
width: 3,
height: LayoutCardHeight.SMALL,
disableDelete: true
},
{
id: getRandomId(),
type: "UserInstanceList",
title: t("实例列表"),
meta: {
type: "instance_error"
},
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
}
]
},
{
page: "/404",
items: [
{
id: getRandomId(),
meta: {},
type: "Page404",
title: t("页面未找到"),
width: 6,
height: LayoutCardHeight.MINI,
disableDelete: true
}
]
}
];

View File

@ -1,11 +1,11 @@
import { h, ref, createVNode, type Ref, reactive } from "vue";
import { t } from "@/lang/i18n";
import type { TableProps, UploadProps } from "ant-design-vue";
import { message, Modal } from "ant-design-vue";
import type { UploadProps } from "ant-design-vue";
import type { Key } from "ant-design-vue/es/table/interface";
import { h, ref, createVNode, reactive } from "vue";
import { LoadingOutlined, ExclamationCircleOutlined } from "@ant-design/icons-vue";
import { parseForwardAddress } from "@/tools/protocol";
import { number2permission, permission2number } from "@/tools/permission";
import { t } from "@/lang/i18n";
import {
fileList as getFileListApi,
getFileStatus as getFileStatusApi,
@ -20,9 +20,6 @@ import {
downloadAddress,
changePermission as changePermissionApi
} from "@/services/apis/fileManager";
import { message, Modal } from "ant-design-vue";
import type {
DataType,
OperationForm,
@ -31,28 +28,34 @@ import type {
Permission
} from "@/types/fileManager";
export const useFileManager = (
operationForm: Ref<OperationForm>,
breadcrumbs: Breadcrumb[],
dataSource: Ref<DataType[] | undefined>,
clipboard: Ref<
| {
type: "copy" | "move";
value: string[];
}
| undefined
>,
selectionData: Ref<DataType[] | undefined>,
instanceId?: string,
daemonId?: string
) => {
export const useFileManager = (instanceId?: string, daemonId?: string) => {
const indicator = h(LoadingOutlined, {
style: {
fontSize: "24px"
}
});
const dataSource = ref<DataType[]>();
const fileStatus = ref<FileStatus>();
const selectedRowKeys = ref<Key[]>([]);
const selectionData = ref<DataType[]>();
const operationForm = ref<OperationForm>({
name: "",
current: 1,
pageSize: 100,
total: 0
});
const breadcrumbs = reactive<Breadcrumb[]>([]);
breadcrumbs.push({
path: "/",
name: "/",
disabled: false
});
const clipboard = ref<{
type: "copy" | "move";
value: string[];
}>();
const dialog = ref({
show: false,
@ -272,7 +275,7 @@ export const useFileManager = (
const zipFile = async () => {
if (!selectionData.value || selectionData.value.length === 0)
return message.error(t("请先选择文件"));
const filename = await openDialog(t("压缩文件"), 't("请输入压缩后的文件名")', "", "zip");
const filename = await openDialog(t("压缩文件"), t("请输入压缩后的文件名"), "", "zip");
const { execute } = compressFileApi();
try {
await execute({
@ -364,10 +367,9 @@ export const useFileManager = (
return false;
};
const rowSelection: TableProps["rowSelection"] = {
onChange: (selectedRowKeys: any, selectedRows: DataType[]) => {
selectionData.value = selectedRows;
}
const selectChanged = (_selectedRowKeys: Key[], selectedRows: DataType[]) => {
selectionData.value = selectedRows;
selectedRowKeys.value = _selectedRowKeys;
};
const rowClickTable = async (item: string, type: number) => {
@ -414,6 +416,8 @@ export const useFileManager = (
};
const handleTableChange = (e: { current: number; pageSize: number }) => {
selectedRowKeys.value = [];
selectionData.value = [];
operationForm.value.current = e.current;
operationForm.value.pageSize = e.pageSize;
getFileList();
@ -498,8 +502,13 @@ export const useFileManager = (
dialog,
percentComplete,
spinning,
rowSelection,
operationForm,
dataSource,
breadcrumbs,
permission,
clipboard,
selectedRowKeys,
selectChanged,
openDialog,
getFileList,
touchFile,

View File

@ -25,6 +25,7 @@ const { updateUserInfo, state } = useAppStateStore();
async function checkPanelStatus() {
const status = await panelStatus().execute();
state.isInstall = status.value?.isInstall ?? true;
state.versionChanged = status.value?.versionChange ? true : false;
if (!state.isInstall) {
return router.push({
path: "/init"
@ -34,10 +35,11 @@ async function checkPanelStatus() {
async function index() {
try {
await initLayoutConfig();
const { execute: reqUserInfo } = userInfoApi();
const info = await reqUserInfo();
updateUserInfo(info.value);
await initLayoutConfig();
} catch (err) {
console.error("Init user info Error:", err);
} finally {

View File

@ -76,8 +76,6 @@ class ApiService {
private async sendRequest(reqId: string, config: RequestConfig) {
try {
const startTime = Date.now();
console.debug(`[ApiService] Request: ${config.url} \n Full AxiosRequestConfig:`, config);
if (!config.timeout) config.timeout = 1000 * 10;
const result = await axios(config);
const endTime = Date.now();
@ -91,8 +89,6 @@ class ApiService {
data: realData
});
console.debug("请求响应缓存表长度:", this.responseMap.size);
this.event.emit(reqId, realData);
} catch (error: AxiosError | Error | any) {
const axiosErr = error as AxiosError;

View File

@ -37,6 +37,7 @@ export const panelStatus = useDefineApi<
{
isInstall: boolean;
language: string;
versionChange?: string;
}
>({
url: "/api/auth/status",

View File

@ -7,6 +7,7 @@ import type { BaseUserInfo } from "@/types/user";
interface AppStateInfo {
userInfo: BaseUserInfo | null;
isInstall: boolean;
versionChanged: boolean;
}
export const useAppStateStore = createGlobalState(() => {
@ -14,7 +15,8 @@ export const useAppStateStore = createGlobalState(() => {
const state: AppStateInfo = reactive<AppStateInfo>({
userInfo: null,
isInstall: true
isInstall: true,
versionChanged: false
});
const cloneState = (): AppStateInfo => {

View File

@ -1,36 +1,15 @@
import type { IGlobalInstanceConfig } from "../../../common/global";
import type { LayoutCardHeight } from "@/config/originLayoutConfig";
import type {
IGlobalInstanceConfig,
ILayoutCard as GlobalLayoutCard,
ILayoutCardParams as GlobalLayoutCardParams,
IJsonData,
IMapData
} from "../../../common/global";
export interface JsonData {
[key: string]: any;
}
export interface MapData<T> {
[key: string]: T;
}
export interface LayoutCardParams {
field: string;
label: string;
type: "string" | "number" | "boolean" | "instanceId";
}
export interface LayoutCard {
id: string;
type: string;
title: string;
width: number;
height: LayoutCardHeight;
meta: JsonData;
disableAdd?: boolean;
onlyPath?: string[];
params?: LayoutCardParams[];
followId?: string;
description?: string;
allowedPages?: Array<string> | null;
line?: number;
disableDelete?: boolean;
}
export type JsonData = IJsonData;
export type MapData<T> = IMapData<T>;
export type LayoutCardParams = GlobalLayoutCardParams;
export type LayoutCard = GlobalLayoutCard;
export interface LayoutWithRouter {
page: string;

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import CardPanel from "@/components/CardPanel.vue";
import type { LayoutCard } from "@/types/index";
import { ref, computed, reactive, onMounted, watch } from "vue";
import { ref, computed, onMounted, watch } from "vue";
import { t } from "@/lang/i18n";
import { convertFileSize } from "@/tools/fileSize";
import dayjs from "dayjs";
@ -12,11 +12,9 @@ import { arrayFilter } from "@/tools/array";
import { useLayoutCardTools } from "@/hooks/useCardTools";
import { throttle } from "lodash";
import { getExtName, getFileIcon } from "@/tools/fileManager";
import { useFileManager } from "@/hooks/usefileManager";
import { useFileManager } from "@/hooks/useFileManager";
import FileEditor from "./dialogs/FileEditor.vue";
import type { DataType, OperationForm, Breadcrumb } from "@/types/fileManager";
import type { DataType } from "@/types/fileManager";
const props = defineProps<{
card: LayoutCard;
@ -28,24 +26,36 @@ const daemonId = getMetaOrRouteValue("daemonId");
const screen = useScreen();
const operationForm = ref<OperationForm>({
name: "",
current: 1,
pageSize: 10,
total: 0
});
const selectionData = ref<DataType[]>();
const dataSource = ref<DataType[]>();
const breadcrumbs = reactive<Breadcrumb[]>([]);
breadcrumbs.push({
path: "/",
name: "/",
disabled: false
});
const {
indicator,
dialog,
percentComplete,
spinning,
fileStatus,
permission,
selectedRowKeys,
operationForm,
dataSource,
breadcrumbs,
clipboard,
selectChanged,
getFileList,
touchFile,
reloadList,
setClipBoard,
paste,
resetName,
deleteFile,
zipFile,
unzipFile,
beforeUpload,
downloadFile,
handleChangeDir,
rowClickTable,
handleTableChange,
getFileStatus,
changePermission
} = useFileManager(instanceId, daemonId);
const columns = computed(() => {
return arrayFilter([
@ -105,45 +115,6 @@ const columns = computed(() => {
]);
});
const clipboard = ref<{
type: "copy" | "move";
value: string[];
}>();
const {
indicator,
dialog,
percentComplete,
rowSelection,
spinning,
fileStatus,
permission,
getFileList,
touchFile,
reloadList,
setClipBoard,
paste,
resetName,
deleteFile,
zipFile,
unzipFile,
beforeUpload,
downloadFile,
handleChangeDir,
rowClickTable,
handleTableChange,
getFileStatus,
changePermission
} = useFileManager(
operationForm,
breadcrumbs,
dataSource,
clipboard,
selectionData,
instanceId,
daemonId
);
watch(
() => operationForm.value.name,
throttle(() => {
@ -295,7 +266,10 @@ onMounted(() => {
</p>
<a-spin :spinning="spinning" :indicator="indicator">
<a-table
:row-selection="rowSelection"
:row-selection="{
selectedRowKeys: selectedRowKeys,
onChange: selectChanged
}"
:row-key="(record: DataType) => record.name"
:data-source="dataSource"
:columns="columns"

View File

@ -3,7 +3,6 @@
"exclude": [],
"compilerOptions": {
"composite": true,
"lib": [],
"types": ["node", "jsdom"]
}
}

View File

@ -7,10 +7,8 @@ interface IClassz {
}
class FileStorageSubsystem implements IStorage {
public static readonly STIRAGE_DATA_PATH = path.normalize(path.join(process.cwd(), "data"));
public static readonly STIRAGE_INDEX_PATH = path.normalize(
path.join(process.cwd(), "data", "index")
);
public static readonly DATA_PATH = path.normalize(path.join(process.cwd(), "data"));
public static readonly INDEX_PATH = path.normalize(path.join(process.cwd(), "data", "index"));
private checkFileName(name: string) {
const blackList = ["\\", "/", ".."];
@ -22,7 +20,7 @@ class FileStorageSubsystem implements IStorage {
// Stored in local file based on class definition and identifier
public async store(category: string, uuid: string, object: any) {
const dirPath = path.join(FileStorageSubsystem.STIRAGE_DATA_PATH, category);
const dirPath = path.join(FileStorageSubsystem.DATA_PATH, category);
if (!fs.existsSync(dirPath)) fs.mkdirsSync(dirPath);
if (!this.checkFileName(uuid))
throw new Error(`UUID ${uuid} does not conform to specification`);
@ -53,7 +51,7 @@ class FileStorageSubsystem implements IStorage {
* Instantiate an object based on the class definition and identifier
*/
public async load(category: string, classz: any, uuid: string) {
const dirPath = path.join(FileStorageSubsystem.STIRAGE_DATA_PATH, category);
const dirPath = path.join(FileStorageSubsystem.DATA_PATH, category);
if (!fs.existsSync(dirPath)) fs.mkdirsSync(dirPath);
if (!this.checkFileName(uuid))
throw new Error(`UUID ${uuid} does not conform to specification`);
@ -73,7 +71,7 @@ class FileStorageSubsystem implements IStorage {
* Return all identifiers related to this class through the class definition
*/
public async list(category: string) {
const dirPath = path.join(FileStorageSubsystem.STIRAGE_DATA_PATH, category);
const dirPath = path.join(FileStorageSubsystem.DATA_PATH, category);
if (!fs.existsSync(dirPath)) fs.mkdirsSync(dirPath);
const files = fs.readdirSync(dirPath);
const result = new Array<string>();
@ -87,7 +85,7 @@ class FileStorageSubsystem implements IStorage {
* Delete an identifier instance of the specified type through the class definition
*/
public async delete(category: string, uuid: string) {
const filePath = path.join(FileStorageSubsystem.STIRAGE_DATA_PATH, category, `${uuid}.json`);
const filePath = path.join(FileStorageSubsystem.DATA_PATH, category, `${uuid}.json`);
if (!fs.existsSync(filePath)) return;
fs.removeSync(filePath);
}

View File

@ -1,15 +1,9 @@
import path from "path";
import fs from "fs-extra";
interface IClassz {
name: string;
}
class StorageSubsystem {
public static readonly STIRAGE_DATA_PATH = path.normalize(path.join(process.cwd(), "data"));
public static readonly STIRAGE_INDEX_PATH = path.normalize(
path.join(process.cwd(), "data", "index")
);
public static readonly DATA_PATH = path.normalize(path.join(process.cwd(), "data"));
public static readonly INDEX_PATH = path.normalize(path.join(process.cwd(), "data", "index"));
private checkFileName(name: string) {
const blackList = ["\\", "/", ".."];
@ -19,9 +13,24 @@ class StorageSubsystem {
return true;
}
public writeFile(name: string, data: string) {
const targetPath = path.normalize(path.join(StorageSubsystem.DATA_PATH, name));
fs.writeFileSync(targetPath, data, { encoding: "utf-8" });
}
public readFile(name: string) {
const targetPath = path.normalize(path.join(StorageSubsystem.DATA_PATH, name));
return fs.readFileSync(targetPath, { encoding: "utf-8" });
}
public fileExists(name: string) {
const targetPath = path.normalize(path.join(StorageSubsystem.DATA_PATH, name));
return fs.existsSync(targetPath);
}
// Stored in local file based on class definition and identifier
public store(category: string, uuid: string, object: any) {
const dirPath = path.join(StorageSubsystem.STIRAGE_DATA_PATH, category);
const dirPath = path.join(StorageSubsystem.DATA_PATH, category);
if (!fs.existsSync(dirPath)) fs.mkdirsSync(dirPath);
if (!this.checkFileName(uuid))
throw new Error(`UUID ${uuid} does not conform to specification`);
@ -52,7 +61,7 @@ class StorageSubsystem {
* Instantiate an object based on the class definition and identifier
*/
public load(category: string, classz: any, uuid: string) {
const dirPath = path.join(StorageSubsystem.STIRAGE_DATA_PATH, category);
const dirPath = path.join(StorageSubsystem.DATA_PATH, category);
if (!fs.existsSync(dirPath)) fs.mkdirsSync(dirPath);
if (!this.checkFileName(uuid))
throw new Error(`UUID ${uuid} does not conform to specification`);
@ -72,7 +81,7 @@ class StorageSubsystem {
* Return all identifiers related to this class through the class definition
*/
public list(category: string) {
const dirPath = path.join(StorageSubsystem.STIRAGE_DATA_PATH, category);
const dirPath = path.join(StorageSubsystem.DATA_PATH, category);
if (!fs.existsSync(dirPath)) fs.mkdirsSync(dirPath);
const files = fs.readdirSync(dirPath);
const result = new Array<string>();
@ -86,7 +95,7 @@ class StorageSubsystem {
* Delete an identifier instance of the specified type through the class definition
*/
public delete(category: string, uuid: string) {
const filePath = path.join(StorageSubsystem.STIRAGE_DATA_PATH, category, `${uuid}.json`);
const filePath = path.join(StorageSubsystem.DATA_PATH, category, `${uuid}.json`);
if (!fs.existsSync(filePath)) return;
fs.removeSync(filePath);
}

View File

@ -1,17 +1,14 @@
import Router from "@koa/router";
import remoteService from "../../service/system_remote_service";
import { setImmediate } from "timers";
import permission from "../../middleware/permission";
import validator from "../../middleware/validator";
import { saveSystemConfig, systemConfig } from "../../setting";
import { logger } from "../../service/log";
import { i18next } from "../../i18n";
import userSystem from "../../service/system_user";
import * as fs from "fs-extra";
import path from "path";
import { getFrontendLayoutConfig, setFrontendLayoutConfig } from "../../service/frontend_layout";
const router = new Router({ prefix: "/overview" });
const LAYOUT_CONFIG_PATH = path.normalize(path.join(process.cwd(), "data", "layout.json"));
// [Top-level Permission]
// Get panel configuration items
@ -69,14 +66,14 @@ router.put("/install", async (ctx) => {
ctx.body = new Error("The MCSManager has been installed");
});
router.get("/layout", permission({ level: 1 }), async (ctx) => {
const layoutConfig = fs.readFileSync(LAYOUT_CONFIG_PATH, "utf-8");
ctx.body = layoutConfig;
// [Public router]
router.get("/layout", async (ctx) => {
ctx.body = getFrontendLayoutConfig();
});
router.post("/layout", permission({ level: 10 }), async (ctx) => {
const config = ctx.request.body;
fs.writeFileSync(LAYOUT_CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
setFrontendLayoutConfig(config);
ctx.body = true;
});

View File

@ -10,6 +10,7 @@ import userSystem from "../../service/system_user";
import { logger } from "../../service/log";
import { $t } from "../../i18n";
import axios from "axios";
import GlobalVariable from "../../common/global_variable";
const router = new Router({ prefix: "/auth" });
// [Public Permission]
@ -66,6 +67,7 @@ router.all(
isInstall = false;
}
ctx.body = {
versionChange: GlobalVariable.get("versionChange", null),
isInstall,
language: systemConfig.language || null
};

View File

@ -0,0 +1,491 @@
import { v4 } from "uuid";
import { IPageLayoutConfig } from "../../../../common/global";
import { $t as t } from "../i18n";
import fs from "fs-extra";
import path from "path";
import storage from "../common/system_storage";
const LAYOUT_CONFIG_NAME = "layout.json";
function getRandomId() {
return v4();
}
export function getFrontendLayoutConfig(): string {
let layoutConfig: string;
if (storage.fileExists(LAYOUT_CONFIG_NAME)) {
layoutConfig = storage.readFile(LAYOUT_CONFIG_NAME);
}
if (layoutConfig) {
return layoutConfig as string;
} else {
setFrontendLayoutConfig(DEFAULT_FRONTEND_LAYOUT_CONFIG);
return getFrontendLayoutConfig();
}
}
export function setFrontendLayoutConfig(config: IPageLayoutConfig[]) {
storage.writeFile(LAYOUT_CONFIG_NAME, JSON.stringify(config, null, 2));
}
export enum LayoutCardHeight {
MINI = "100px",
SMALL = "200px",
MEDIUM = "400px",
BIG = "600px",
LARGE = "800px",
AUTO = "unset"
}
// 默认配置
export const DEFAULT_FRONTEND_LAYOUT_CONFIG: IPageLayoutConfig[] = [
{
page: "/",
items: [
{
id: getRandomId(),
type: "StatusBlock",
title: t("TXT_CODE_e627e546"),
meta: {
type: "node"
},
width: 3,
description: t("TXT_CODE_55ade942"),
height: LayoutCardHeight.SMALL
},
{
id: getRandomId(),
type: "StatusBlock",
title: t("TXT_CODE_88e9361a"),
meta: {
type: "instance"
},
width: 3,
description: t("TXT_CODE_55ade942"),
height: LayoutCardHeight.SMALL
},
{
id: getRandomId(),
type: "StatusBlock",
title: t("TXT_CODE_db64faf6"),
meta: {
type: "users"
},
width: 3,
description: t("TXT_CODE_55ade942"),
height: LayoutCardHeight.SMALL
},
{
id: getRandomId(),
type: "StatusBlock",
title: t("TXT_CODE_66056676"),
meta: {
type: "system"
},
width: 3,
description: t("TXT_CODE_55ade942"),
height: LayoutCardHeight.SMALL
},
{
id: getRandomId(),
meta: {},
type: "DataOverview",
title: t("TXT_CODE_721157a3"),
width: 9,
description: t("TXT_CODE_55ade942"),
height: LayoutCardHeight.MEDIUM
},
{
id: getRandomId(),
meta: {},
type: "QuickStart",
title: t("TXT_CODE_2799a1dd"),
width: 3,
description: t("TXT_CODE_55ade942"),
height: LayoutCardHeight.MEDIUM
},
{
id: getRandomId(),
meta: {},
type: "RequestChart",
title: t("TXT_CODE_a4037a98"),
width: 6,
description: t("TXT_CODE_55ade942"),
height: LayoutCardHeight.SMALL
},
{
id: getRandomId(),
meta: {},
type: "InstanceChart",
title: t("TXT_CODE_d6d9c42c"),
width: 6,
description: t("TXT_CODE_55ade942"),
height: LayoutCardHeight.SMALL
},
{
id: getRandomId(),
meta: {},
type: "NodeOverview",
title: t("TXT_CODE_bfb50126"),
width: 12,
description: t("TXT_CODE_55ade942"),
height: LayoutCardHeight.MEDIUM
}
]
},
{
page: "/instances",
items: [
{
id: getRandomId(),
meta: {},
type: "InstanceList",
title: t("TXT_CODE_e21473bc"),
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/instances/terminal",
items: [
{
id: getRandomId(),
meta: {
viewType: "inner"
},
type: "Terminal",
title: t("TXT_CODE_4ccdd3a0"),
width: 12,
height: LayoutCardHeight.BIG,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "InstanceBaseInfo",
title: t("TXT_CODE_eadb4f60"),
width: 4,
height: LayoutCardHeight.SMALL
},
{
id: getRandomId(),
meta: {},
type: "InstanceManagerBtns",
title: t("TXT_CODE_efd37c48"),
width: 8,
height: LayoutCardHeight.SMALL,
disableDelete: true
}
]
},
{
page: "/instances/terminal/files",
items: [
{
id: getRandomId(),
meta: {},
type: "InstanceFileManager",
title: t("文件管理"),
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/instances/terminal/serverConfig",
items: [
{
id: getRandomId(),
meta: {},
type: "InstanceServerConfigOverview",
title: t("服务端配置文件"),
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/instances/terminal/serverConfig/fileEdit",
items: [
{
id: getRandomId(),
meta: {},
type: "InstanceServerConfigFile",
title: t("编辑服务端配置文件"),
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/users",
items: [
{
id: getRandomId(),
meta: {},
type: "UserList",
title: t("用户列表"),
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/users/config",
items: [
{
id: getRandomId(),
meta: {},
type: "UserAccessSettings",
title: t("用户权限设定"),
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/settings",
items: [
{
id: getRandomId(),
meta: {},
type: "Settings",
title: t("系统设置"),
width: 8,
height: LayoutCardHeight.MEDIUM,
disableDelete: true
}
]
},
{
page: "/node",
items: [
{
id: getRandomId(),
meta: {},
type: "NodeList",
title: t("TXT_CODE_20509fa0"),
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/node/image",
items: [
{
id: getRandomId(),
meta: {},
type: "ImageManager",
title: t("镜像管理"),
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/node/image/new",
items: [
{
id: getRandomId(),
meta: {},
type: "NewImage",
title: t("创建镜像"),
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/quickstart",
items: [
{
id: getRandomId(),
meta: {},
type: "QuickStartFlow",
title: t("TXT_CODE_9b99b72e"),
width: 8,
height: LayoutCardHeight.AUTO
}
]
},
{
page: "/quickstart/minecraft",
items: [
{
id: getRandomId(),
meta: {},
type: "McPreset",
title: "",
width: 12,
height: LayoutCardHeight.AUTO
},
{
id: getRandomId(),
meta: {},
type: "EmptyCard",
title: "",
width: 12,
height: LayoutCardHeight.MINI
}
]
},
{
page: "/customer",
items: [
{
id: getRandomId(),
type: "UserStatusBlock",
title: t("实例总计"),
meta: {
type: "instance_all"
},
width: 3,
height: LayoutCardHeight.SMALL,
disableDelete: true
},
{
id: getRandomId(),
type: "UserStatusBlock",
title: t("正在运行"),
meta: {
type: "instance_running"
},
width: 3,
height: LayoutCardHeight.SMALL,
disableDelete: true
},
{
id: getRandomId(),
type: "UserStatusBlock",
title: t("未运行"),
meta: {
type: "instance_stop"
},
width: 3,
height: LayoutCardHeight.SMALL,
disableDelete: true
},
{
id: getRandomId(),
type: "UserStatusBlock",
title: t("维护中"),
meta: {
type: "instance_error"
},
width: 3,
height: LayoutCardHeight.SMALL,
disableDelete: true
},
{
id: getRandomId(),
type: "UserInstanceList",
title: t("实例列表"),
meta: {
type: "instance_error"
},
width: 12,
height: LayoutCardHeight.AUTO,
disableDelete: true
}
]
},
{
page: "/404",
items: [
{
id: getRandomId(),
meta: {},
type: "Page404",
title: t("页面未找到"),
width: 6,
height: LayoutCardHeight.MINI,
disableDelete: true
}
]
}
];

View File

@ -1,21 +1,42 @@
import * as fs from "fs-extra";
import GlobalVariable from "./common/global_variable";
import { logger } from "./service/log";
import path from "path";
import storage from "./common/system_storage";
interface PackageInfo {
name: string;
version: string;
daemonVersion: string;
description: string;
}
const PACKAGE_JSON = "package.json";
const VERSION_LOG_TEXT_NAME = "current-version.txt";
let currentVersion = "";
export function initVersionManager() {
try {
GlobalVariable.set("version", "Unknown");
if (fs.existsSync(PACKAGE_JSON)) {
const data: any = JSON.parse(fs.readFileSync(PACKAGE_JSON, { encoding: "utf-8" }));
const data: PackageInfo = JSON.parse(fs.readFileSync(PACKAGE_JSON, { encoding: "utf-8" }));
if (data.version) {
GlobalVariable.set("version", data.version);
currentVersion = String(data.version);
}
}
} catch (error) {
logger.error("Version Check failure:", error);
}
if (currentVersion && storage.fileExists(VERSION_LOG_TEXT_NAME)) {
const LastLaunchedVersion = storage.readFile(VERSION_LOG_TEXT_NAME);
if (LastLaunchedVersion && LastLaunchedVersion != currentVersion) {
logger.info(`Version changed from ${LastLaunchedVersion} to ${currentVersion}`);
GlobalVariable.set("versionChange", currentVersion);
}
}
storage.writeFile(VERSION_LOG_TEXT_NAME, currentVersion);
}
export function getVersion(): string {