feat: Optimize frontend structure (#12475)

This commit is contained in:
CityFun
2026-04-13 05:48:42 +00:00
committed by GitHub
parent 4d06f444ac
commit 89888cef73
221 changed files with 2740 additions and 1291 deletions
+3 -2
View File
@@ -7,7 +7,8 @@
"dev": "node --max-old-space-size=8192 ./node_modules/.bin/vite --host",
"serve": "vite",
"build:dev": "vite build --mode development",
"build:test": "vue-tsc --noEmit && vite build --mode test",
"type-check": "node --max-old-space-size=8192 ./node_modules/vue-tsc/bin/vue-tsc.js --noEmit",
"build:test": "npm run type-check && vite build --mode test",
"build:pro": "vite build --mode production",
"preview": "vite preview",
"lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
@@ -68,6 +69,7 @@
"@vitejs/plugin-vue-jsx": "^5.1.1",
"autoprefixer": "^10.4.7",
"commitizen": "^4.2.4",
"esbuild": "^0.27.3",
"eslint": "^8.57.0",
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-prettier": "^4.2.1",
@@ -88,7 +90,6 @@
"vite-plugin-compression": "^0.5.1",
"vite-plugin-eslint2": "^5.0.3",
"vite-plugin-html": "^3.2.2",
"vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-vue-setup-extend": "^0.4.0",
"vite-svg-loader": "^5.1.0",
"vue-tsc": "^0.29.8"
+1 -2
View File
@@ -8,8 +8,7 @@ import { MsgError } from '@/utils/message';
import { Base64 } from 'js-base64';
import i18n from '@/lang';
import { changeToLocal } from '@/utils/node';
import { getCookie } from '@/utils/util';
import { getCookie } from '@/utils/auth';
const globalStore = GlobalStore();
const config = {
+1 -2
View File
@@ -1,8 +1,7 @@
import http from '@/api';
import { ResPage } from '@/api/interface';
import { Alert } from '../interface/alert';
import { deepCopy } from '@/utils/util';
import { deepCopy } from '@/utils/misc';
export const SearchAlerts = (req: Alert.AlertSearch) => {
return http.post<ResPage<Alert.AlertInfo>>(`/alert/search`, req);
};
+1 -1
View File
@@ -1,5 +1,5 @@
import http from '@/api';
import { deepCopy } from '@/utils/util';
import { deepCopy } from '@/utils/misc';
import { Base64 } from 'js-base64';
import { ResPage } from '../interface';
import { Backup } from '../interface/backup';
+1 -1
View File
@@ -1,5 +1,5 @@
import http from '@/api';
import { deepCopy } from '@/utils/util';
import { deepCopy } from '@/utils/misc';
import { Base64 } from 'js-base64';
import { ResPage, DescriptionUpdate } from '../interface';
import { Database } from '../interface/database';
+1 -1
View File
@@ -2,7 +2,7 @@ import http from '@/api';
import { ResPage, ReqPage } from '../interface';
import { Host } from '../interface/host';
import { TimeoutEnum } from '@/enums/http-enum';
import { deepCopy } from '@/utils/util';
import { deepCopy } from '@/utils/misc';
import { Base64 } from 'js-base64';
// firewall
+1 -1
View File
@@ -1,5 +1,5 @@
import http from '@/api';
import { deepCopy } from '@/utils/util';
import { deepCopy } from '@/utils/misc';
import { Base64 } from 'js-base64';
import { ResPage, SearchWithPage, DescriptionUpdate, ReqPage } from '../interface';
import { Setting } from '../interface/setting';
+1 -2
View File
@@ -2,8 +2,7 @@ import http from '@/api';
import { ResPage } from '../interface';
import { Host } from '../interface/host';
import { Base64 } from 'js-base64';
import { deepCopy } from '@/utils/util';
import { deepCopy } from '@/utils/misc';
export const searchHosts = (params: Host.SearchWithPage) => {
return http.postLocalNode<ResPage<Host.Host>>(`/hosts/search`, params);
};
+1 -2
View File
@@ -3,8 +3,7 @@ import { ReqPage, ResPage, UpdateByFile } from '../interface';
import { Toolbox } from '../interface/toolbox';
import { Base64 } from 'js-base64';
import { TimeoutEnum } from '@/enums/http-enum';
import { deepCopy } from '@/utils/util';
import { deepCopy } from '@/utils/misc';
// device
export const getDeviceBase = () => {
return http.post<Toolbox.DeviceBaseInfo>(`/toolbox/device/base`, {}, TimeoutEnum.T_60S);
+1 -1
View File
@@ -3,7 +3,7 @@ import { ReqPage, ResPage } from '../interface';
import { Website } from '../interface/website';
import { File } from '../interface/file';
import { TimeoutEnum } from '@/enums/http-enum';
import { deepCopy } from '@/utils/util';
import { deepCopy } from '@/utils/misc';
import { Base64 } from 'js-base64';
export const searchWebsites = (req: Website.WebSiteSearch, node?: string) => {
+5 -1
View File
@@ -173,7 +173,11 @@
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { computeSize, dateFormat, downloadFile, newUUID, transferTimeToSecond } from '@/utils/util';
import { computeSize } from '@/utils/size';
import { dateFormat } from '@/utils/date';
import { downloadFile } from '@/utils/file';
import { newUUID } from '@/utils/id';
import { transferTimeToSecond } from '@/utils/validate';
import {
getLocalBackupDir,
handleBackup,
@@ -4,7 +4,7 @@
</template>
<script lang="ts" setup>
import { copyText } from '@/utils/util';
import { copyText } from '@/utils/clipboard';
defineOptions({ name: 'CopyButton' });
defineProps({
@@ -17,7 +17,7 @@
</template>
<script setup lang="ts" name="404">
import { copyText } from '@/utils/util';
import { copyText } from '@/utils/clipboard';
</script>
<style scoped lang="scss">
@@ -17,7 +17,7 @@
</template>
<script setup lang="ts" name="404">
import { copyText } from '@/utils/util';
import { copyText } from '@/utils/clipboard';
</script>
<style scoped lang="scss">
@@ -17,7 +17,7 @@
</template>
<script setup lang="ts" name="404">
import { copyText } from '@/utils/util';
import { copyText } from '@/utils/clipboard';
</script>
<style scoped lang="scss">
+1 -2
View File
@@ -30,8 +30,7 @@
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { computeSize } from '@/utils/util';
import { computeSize } from '@/utils/size';
const dialogVisible = ref();
const existFiles = ref<DialogProps[]>([]);
+2 -2
View File
@@ -178,8 +178,8 @@ import { nextTick, onUpdated, reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgSuccess, MsgWarning } from '@/utils/message';
import { useSearchableForSelect } from '@/views/host/file-management/hooks/searchable';
import { computeSize, debounce } from '@/utils/util';
import { computeSize } from '@/utils/size';
import { debounce } from '@/utils/misc';
const data = ref([]);
const loading = ref(false);
const paths = ref<string[]>([]);
@@ -0,0 +1,92 @@
<template>
<div class="fu-input-rw-switch">
<div
v-if="!isWrite"
class="fu-input-rw-switch__read"
@click.stop="handleReadClick"
@dblclick.stop="handleReadDblClick"
>
<span class="fu-input-rw-switch__text">{{ displayValue }}</span>
</div>
<el-input
v-else
ref="inputRef"
v-bind="$attrs"
:model-value="modelValue"
@update:model-value="handleUpdate"
@input="handleInput"
@blur="handleBlur"
@keyup.enter="handleEnter"
@click.stop
/>
</div>
</template>
<script setup lang="ts">
import { computed, nextTick, ref } from 'vue';
defineOptions({ name: 'FuInputRwSwitch', inheritAttrs: false });
const props = defineProps({
modelValue: {
type: [String, Number],
default: '',
},
writeTrigger: {
type: String,
default: 'onClick',
},
});
const emit = defineEmits(['update:modelValue', 'input', 'blur', 'enter']);
const inputRef = ref();
const isWrite = ref(false);
const displayValue = computed(() => {
return props.modelValue === '' || props.modelValue === undefined || props.modelValue === null
? '-'
: String(props.modelValue);
});
const openWrite = async () => {
isWrite.value = true;
await nextTick();
inputRef.value?.focus?.();
inputRef.value?.select?.();
};
const closeWrite = () => {
isWrite.value = false;
};
const handleReadClick = () => {
if (props.writeTrigger === 'onClick') {
openWrite();
}
};
const handleReadDblClick = () => {
if (props.writeTrigger === 'onDblclick') {
openWrite();
}
};
const handleUpdate = (value: string | number) => {
emit('update:modelValue', value);
};
const handleInput = (value: string) => {
emit('input', value);
};
const handleBlur = (event: FocusEvent) => {
emit('blur', event);
closeWrite();
};
const handleEnter = (event: KeyboardEvent) => {
emit('enter', event);
closeWrite();
};
</script>
@@ -0,0 +1,76 @@
<template>
<div class="fu-read-write-switch">
<div
v-if="!isWrite"
class="fu-read-write-switch__read"
@click.stop="handleReadClick"
@dblclick.stop="handleReadDblClick"
>
<slot name="read" :write="openWrite">
<span class="fu-read-write-switch__text">{{ displayValue }}</span>
</slot>
</div>
<div v-else class="fu-read-write-switch__write" @click.stop>
<slot :read="closeWrite"></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
defineOptions({ name: 'FuReadWriteSwitch' });
const props = defineProps({
modelValue: {
type: [String, Number, Boolean, Object, Array],
default: '',
},
data: {
type: [String, Number, Boolean, Object, Array],
default: '',
},
writeTrigger: {
type: String,
default: 'onClick',
},
});
const emit = defineEmits(['update:modelValue', 'change']);
const isWrite = ref(false);
const displayValue = computed(() => {
const value = props.modelValue !== '' && props.modelValue !== undefined ? props.modelValue : props.data;
return value === '' || value === undefined || value === null ? '-' : String(value);
});
const openWrite = () => {
isWrite.value = true;
};
const closeWrite = (value?: any) => {
if (value !== undefined) {
emit('update:modelValue', value);
emit('change', value);
}
isWrite.value = false;
};
const handleReadClick = () => {
if (props.writeTrigger === 'onClick') {
openWrite();
}
};
const handleReadDblClick = () => {
if (props.writeTrigger === 'onDblclick') {
openWrite();
}
};
defineExpose({
write: openWrite,
read: closeWrite,
});
</script>
@@ -0,0 +1,104 @@
<template>
<div class="fu-select-rw-switch">
<div
v-if="!isWrite"
class="fu-select-rw-switch__read"
@click.stop="handleReadClick"
@dblclick.stop="handleReadDblClick"
>
<slot name="read">
<span class="fu-select-rw-switch__text">{{ displayValue }}</span>
</slot>
</div>
<el-select
v-else
ref="selectRef"
v-bind="$attrs"
:model-value="modelValue"
@update:model-value="handleUpdate"
@change="handleChange"
@blur="handleBlur"
@visible-change="handleVisibleChange"
@click.stop
>
<slot></slot>
</el-select>
</div>
</template>
<script setup lang="ts">
import { computed, nextTick, ref } from 'vue';
defineOptions({ name: 'FuSelectRwSwitch', inheritAttrs: false });
const props = defineProps({
modelValue: {
type: [String, Number, Boolean],
default: '',
},
blurTime: {
type: Number,
default: 150,
},
writeTrigger: {
type: String,
default: 'onClick',
},
});
const emit = defineEmits(['update:modelValue', 'input', 'blur', 'change']);
const selectRef = ref();
const isWrite = ref(false);
const displayValue = computed(() => {
return props.modelValue === '' || props.modelValue === undefined || props.modelValue === null
? '-'
: String(props.modelValue);
});
const openWrite = async () => {
isWrite.value = true;
await nextTick();
selectRef.value?.focus?.();
};
const closeWrite = () => {
window.setTimeout(() => {
isWrite.value = false;
}, props.blurTime);
};
const handleReadClick = () => {
if (props.writeTrigger === 'onClick') {
openWrite();
}
};
const handleReadDblClick = () => {
if (props.writeTrigger === 'onDblclick') {
openWrite();
}
};
const handleUpdate = (value: string | number | boolean) => {
emit('update:modelValue', value);
emit('input', value);
};
const handleChange = (value: string | number | boolean) => {
emit('change', value);
closeWrite();
};
const handleBlur = (event: FocusEvent) => {
emit('blur', event);
closeWrite();
};
const handleVisibleChange = (visible: boolean) => {
if (!visible) {
closeWrite();
}
};
</script>
+18
View File
@@ -0,0 +1,18 @@
import { defineComponent, h } from 'vue';
export default defineComponent({
name: 'FuStep',
props: {
id: {
type: String,
default: '',
},
title: {
type: String,
default: '',
},
},
setup(_props, { slots }) {
return () => h('div', slots.default?.());
},
});
+213
View File
@@ -0,0 +1,213 @@
import { computed, defineComponent, h, ref, watch, type PropType, type VNode } from 'vue';
import { flattenVNodes, getVNodeComponentName } from './shared';
interface FuStepItem {
id: string;
index: number;
title: string;
vnode: VNode;
}
const buildStepChildren = (vnode: VNode) => {
const children = vnode.children as Record<string, (() => VNode[]) | undefined> | null;
return children?.default?.() || [];
};
export default defineComponent({
name: 'FuSteps',
props: {
active: {
type: Number,
default: 0,
},
direction: {
type: String,
default: 'horizontal',
},
space: {
type: Number,
default: 0,
},
isLoading: {
type: Boolean,
default: false,
},
finishButtonText: {
type: String,
default: '',
},
beforeLeave: {
type: Function as PropType<
(step: { id: string; index: number; title: string }) => boolean | Promise<boolean>
>,
default: undefined,
},
},
emits: ['change'],
setup(props, { emit, slots, expose }) {
const activeIndex = ref(props.active);
const isRunningBeforeLeave = ref(false);
const stepItems = computed<FuStepItem[]>(() =>
flattenVNodes(slots.default?.() || [])
.filter((vnode) => getVNodeComponentName(vnode) === 'FuStep')
.map((vnode, index) => {
const vnodeProps = (vnode.props || {}) as Record<string, any>;
return {
id: String(vnodeProps.id || index),
index,
title: String(vnodeProps.title || ''),
vnode,
};
}),
);
const emitChange = () => {
const currentStep = stepItems.value[activeIndex.value];
if (!currentStep) {
return;
}
emit('change', {
id: currentStep.id,
index: currentStep.index,
title: currentStep.title,
});
};
watch(
() => props.active,
(value) => {
activeIndex.value = value;
},
);
watch(
stepItems,
(items) => {
if (items.length === 0) {
activeIndex.value = 0;
return;
}
if (activeIndex.value > items.length - 1) {
activeIndex.value = items.length - 1;
}
emitChange();
},
{ immediate: true, deep: true },
);
const runBeforeLeave = async () => {
if (!props.beforeLeave) {
return true;
}
const currentStep = stepItems.value[activeIndex.value];
if (!currentStep) {
return true;
}
if (isRunningBeforeLeave.value) {
return true;
}
isRunningBeforeLeave.value = true;
try {
return (
(await props.beforeLeave({
id: currentStep.id,
index: currentStep.index,
title: currentStep.title,
})) !== false
);
} finally {
isRunningBeforeLeave.value = false;
}
};
const changeTo = async (nextIndex: number, runGuard = true) => {
if (nextIndex < 0 || nextIndex >= stepItems.value.length || nextIndex === activeIndex.value) {
return false;
}
const currentIndex = activeIndex.value;
if (runGuard) {
const canLeave = await runBeforeLeave();
if (activeIndex.value !== currentIndex) {
return true;
}
if (!canLeave) {
return false;
}
}
activeIndex.value = nextIndex;
emitChange();
return true;
};
const next = async () => {
if (isRunningBeforeLeave.value) {
const nextIndex = Math.min(activeIndex.value + 1, stepItems.value.length - 1);
if (nextIndex !== activeIndex.value) {
activeIndex.value = nextIndex;
emitChange();
}
return true;
}
return changeTo(activeIndex.value + 1, true);
};
const prev = async () => {
return changeTo(activeIndex.value - 1, false);
};
expose({
next,
prev,
active: activeIndex,
});
return () => {
const currentStep = stepItems.value[activeIndex.value];
return h('div', { class: ['fu-steps', `fu-steps--${props.direction}`] }, [
h(
'div',
{
class: 'fu-steps__nav',
style: props.direction === 'vertical' && props.space ? { gap: `${props.space}px` } : undefined,
},
stepItems.value.map((step, index) =>
h(
'button',
{
key: step.id,
class: [
'fu-steps__item',
{
'is-active': index === activeIndex.value,
'is-finished': index < activeIndex.value,
},
],
type: 'button',
disabled: props.isLoading,
onClick: () => changeTo(step.index, step.index > activeIndex.value),
},
[
h('span', { class: 'fu-steps__index' }, index + 1),
h('span', { class: 'fu-steps__title' }, step.title),
],
),
),
),
h('div', { class: 'fu-steps__content' }, currentStep ? buildStepChildren(currentStep.vnode) : []),
slots.footer
? h(
'div',
{ class: 'fu-steps__footer' },
slots.footer({
active: currentStep,
next,
prev,
}),
)
: null,
]);
};
},
});
+213
View File
@@ -0,0 +1,213 @@
import { ElTable } from 'element-plus';
import { computed, defineComponent, h, onMounted, ref, watch, type PropType, type VNode } from 'vue';
import {
FU_TABLE_STORAGE_PREFIX,
flattenVNodes,
isElTableColumnVNode,
type FuTableColumnConfig,
updateArrayInPlace,
} from './shared';
const buildColumnKey = (vnode: VNode, index: number) => {
const props = (vnode.props || {}) as Record<string, any>;
return String(props.columnKey || props.prop || props.label || `column-${index}`);
};
const buildColumnConfig = (vnode: VNode, index: number): FuTableColumnConfig => {
const props = (vnode.props || {}) as Record<string, any>;
return {
key: buildColumnKey(vnode, index),
label: String(props.label || props.prop || `column-${index}`),
prop: props.prop,
show: true,
fixed: props.fixed ?? props.fix,
};
};
const isConfigurableColumn = (vnode: VNode) => {
if (!isElTableColumnVNode(vnode)) {
return false;
}
const props = (vnode.props || {}) as Record<string, any>;
if (['selection', 'index', 'expand'].includes(props.type)) {
return false;
}
return Boolean(props.label || props.prop);
};
export default defineComponent({
name: 'FuTable',
props: {
columns: {
type: Array as PropType<FuTableColumnConfig[]>,
default: undefined,
},
localKey: {
type: String,
default: '',
},
refresh: {
type: Boolean,
default: true,
},
refTable: {
type: Object as PropType<{ value?: unknown } | null>,
default: null,
},
},
setup(props, { attrs, slots, expose }) {
const refElTable = ref();
const storageLoaded = ref(false);
const storedColumns = ref<FuTableColumnConfig[]>([]);
const slotChildren = computed(() => flattenVNodes(slots.default?.() || []));
const slotColumns = computed(() =>
slotChildren.value.filter(isConfigurableColumn).map((vnode, index) => buildColumnConfig(vnode, index)),
);
const ensureStoredColumns = () => {
if (storageLoaded.value || !props.localKey || typeof window === 'undefined') {
storageLoaded.value = true;
return;
}
storageLoaded.value = true;
try {
const raw = localStorage.getItem(`${FU_TABLE_STORAGE_PREFIX}${props.localKey}`);
if (!raw) {
return;
}
const parsed = JSON.parse(raw);
if (Array.isArray(parsed)) {
storedColumns.value = parsed;
}
} catch {
storedColumns.value = [];
}
};
const syncColumns = (nextColumns: FuTableColumnConfig[]) => {
if (!props.columns) {
return;
}
ensureStoredColumns();
const currentColumns = props.columns as FuTableColumnConfig[];
const sourceColumns = currentColumns.length > 0 ? currentColumns : storedColumns.value;
const sourceByKey = new Map(sourceColumns.map((column) => [column.key, column]));
const sourceOrder = new Map(sourceColumns.map((column, index) => [column.key, index]));
const mergedColumns = nextColumns
.map((column, index) => ({
...column,
show: sourceByKey.get(column.key)?.show ?? true,
fixed: sourceByKey.get(column.key)?.fixed ?? column.fixed,
__order: sourceOrder.get(column.key) ?? index,
}))
.sort((a, b) => a.__order - b.__order)
.map((column) => {
const normalizedColumn = { ...column };
delete (normalizedColumn as Partial<typeof normalizedColumn> & { __order?: number }).__order;
return normalizedColumn;
});
updateArrayInPlace(currentColumns, mergedColumns);
};
const resolvedColumns = computed(() => {
if (props.columns && props.columns.length > 0) {
return props.columns;
}
if (storedColumns.value.length > 0) {
const storageMap = new Map(storedColumns.value.map((column) => [column.key, column]));
const orderMap = new Map(storedColumns.value.map((column, index) => [column.key, index]));
return [...slotColumns.value]
.map((column, index) => ({
...column,
show: storageMap.get(column.key)?.show ?? true,
__order: orderMap.get(column.key) ?? index,
}))
.sort((a, b) => a.__order - b.__order)
.map((column) => {
const normalizedColumn = { ...column };
delete (normalizedColumn as Partial<typeof normalizedColumn> & { __order?: number }).__order;
return normalizedColumn;
});
}
return slotColumns.value;
});
const orderedConfigurableChildren = computed(() => {
const columnNodeMap = new Map(
slotChildren.value
.filter(isConfigurableColumn)
.map((vnode, index) => [buildColumnKey(vnode, index), vnode]),
);
return resolvedColumns.value
.filter((column) => column.show !== false)
.map((column) => columnNodeMap.get(column.key))
.filter((column): column is VNode => Boolean(column));
});
const renderedChildren = computed(() => {
if (orderedConfigurableChildren.value.length === 0) {
return slotChildren.value;
}
const children: VNode[] = [];
let hasInsertedConfigurableColumns = false;
for (const child of slotChildren.value) {
if (isConfigurableColumn(child)) {
if (!hasInsertedConfigurableColumns) {
children.push(...orderedConfigurableChildren.value);
hasInsertedConfigurableColumns = true;
}
continue;
}
children.push(child);
}
return children;
});
watch(slotColumns, (nextColumns) => syncColumns(nextColumns), {
immediate: true,
deep: true,
});
watch(
() => props.columns,
(nextColumns) => {
if (!props.localKey || !nextColumns || typeof window === 'undefined') {
return;
}
localStorage.setItem(`${FU_TABLE_STORAGE_PREFIX}${props.localKey}`, JSON.stringify(nextColumns));
},
{
deep: true,
},
);
onMounted(() => {
ensureStoredColumns();
if (props.refTable) {
props.refTable.value = refElTable.value;
}
});
expose({ refElTable });
return () =>
h(
ElTable as any,
{
...attrs,
ref: refElTable,
class: ['fu-table', attrs.class],
headerRowClassName: 'fu-table-header',
},
{
...slots,
default: () => renderedChildren.value,
},
);
},
});
@@ -0,0 +1,125 @@
<template>
<div class="fu-search-bar fu-table-column-select">
<el-popover
v-model:visible="visible"
:trigger="trigger"
:popper-class="resolvedPopperClass"
placement="bottom-end"
:width="260"
>
<template #reference>
<el-button class="fu-search-bar-button" :icon="iconComponent" @click.stop>
<span v-if="!onlyIcon">{{ resolvedTitle }}</span>
</el-button>
</template>
<div class="fu-table-column-select__panel" @click.stop>
<div class="fu-table-column-select__title">{{ resolvedTitle }}</div>
<div ref="listRef" class="fu-table-column-select__list">
<div
v-for="column in columns"
:key="column.key"
class="fu-table-column-select__item"
:data-key="column.key"
>
<el-icon class="fu-table-column-select__drag"><Rank /></el-icon>
<el-checkbox v-model="column.show" :disabled="isFixedColumn(column)">
{{ column.label }}
</el-checkbox>
</div>
</div>
</div>
</el-popover>
</div>
</template>
<script setup lang="ts">
import Sortable from 'sortablejs';
import { Rank, Setting } from '@element-plus/icons-vue';
import { computed, nextTick, onBeforeUnmount, ref, watch, type PropType } from 'vue';
import { useI18n } from 'vue-i18n';
import type { FuTableColumnConfig } from './shared';
defineOptions({ name: 'FuTableColumnSelect' });
const { t } = useI18n();
const props = defineProps({
columns: {
type: Array as PropType<FuTableColumnConfig[]>,
default: () => [],
},
title: {
type: String,
default: '',
},
icon: {
type: [String, Object],
default: undefined,
},
onlyIcon: {
type: Boolean,
default: false,
},
trigger: {
type: String,
default: 'click',
},
popperClass: {
type: String,
default: '',
},
});
const visible = ref(false);
const listRef = ref<HTMLElement | null>(null);
let sortable: Sortable | undefined;
const resolvedTitle = computed(() => props.title || t('fu.table.custom_table_rows'));
const resolvedPopperClass = computed(() =>
['fu-table-column-select__popover', props.popperClass].filter(Boolean).join(' '),
);
const iconComponent = computed(() => props.icon || Setting);
const initSortable = async () => {
await nextTick();
sortable?.destroy();
if (!listRef.value) {
return;
}
sortable = Sortable.create(listRef.value, {
animation: 150,
handle: '.fu-table-column-select__drag',
onEnd: ({ oldIndex, newIndex }) => {
if (oldIndex === undefined || newIndex === undefined || oldIndex === newIndex) {
return;
}
const nextColumns = [...props.columns];
const [movedColumn] = nextColumns.splice(oldIndex, 1);
if (!movedColumn) {
return;
}
nextColumns.splice(newIndex, 0, movedColumn);
props.columns.splice(0, props.columns.length, ...nextColumns);
},
});
};
const isFixedColumn = (column: FuTableColumnConfig) => {
return Boolean(column.fixed);
};
watch(visible, (value) => {
if (value) {
initSortable();
return;
}
sortable?.destroy();
sortable = undefined;
});
onBeforeUnmount(() => {
sortable?.destroy();
sortable = undefined;
});
</script>
@@ -0,0 +1,225 @@
<template>
<el-table-column v-bind="$attrs" :label="label" :width="resolvedWidth" :align="align" :fixed="resolvedFixed">
<template #default="{ row }">
<div class="fu-table-operations" @click.stop>
<span
v-for="button in getInlineButtons(row)"
:key="buildButtonKey(button)"
class="fu-table-operations__action"
>
<el-button
class="fu-table-operations__button"
link
:type="button.type || 'primary'"
:disabled="isButtonDisabled(button, row)"
@click.stop="handleButtonClick(button, row)"
>
<el-icon v-if="button.icon" class="mr-1">
<component :is="button.icon" />
</el-icon>
<span>{{ button.label }}</span>
</el-button>
</span>
<span v-if="getMoreButtons(row).length > 0" class="fu-table-operations__action">
<el-dropdown
class="fu-table-more-button"
:trigger="trigger"
@command="(button) => handleButtonClick(button, row)"
>
<span class="fu-table-operations__dropdown-trigger">
<el-button class="fu-table-operations__button" link type="primary" @click.stop>
{{ t('fu.table.more') }}
</el-button>
</span>
<template #dropdown>
<el-dropdown-menu :style="dropdownStyle">
<el-dropdown-item
v-for="button in getMoreButtons(row)"
:key="buildButtonKey(button)"
:command="button"
:disabled="isButtonDisabled(button, row)"
:divided="button.divided"
>
<el-icon v-if="button.icon" class="mr-1">
<component :is="button.icon" />
</el-icon>
<span>{{ button.label }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</span>
</div>
</template>
</el-table-column>
</template>
<script setup lang="ts">
import { computed, type PropType } from 'vue';
import { useI18n } from 'vue-i18n';
import { resolveMaybeFn, type FuTableOperationButton } from './shared';
defineOptions({ name: 'FuTableOperations' });
const { t } = useI18n();
const normalizeWidth = (value?: string | number) => {
if (value === undefined || value === null || value === '') {
return undefined;
}
if (typeof value === 'number') {
return value;
}
const trimmed = value.trim();
if (!trimmed || trimmed === 'auto') {
return undefined;
}
return trimmed;
};
const props = defineProps({
buttons: {
type: Array as PropType<FuTableOperationButton[]>,
default: () => [],
},
label: {
type: String,
default: '',
},
width: {
type: [Number, String],
default: undefined,
},
minWidth: {
type: [Number, String],
default: undefined,
},
align: {
type: String,
default: 'center',
},
ellipsis: {
type: Number,
default: 2,
},
trigger: {
type: String,
default: 'hover',
},
fixed: {
type: [Boolean, String],
default: undefined,
},
fix: {
type: [Boolean, String],
default: undefined,
},
maxHeight: {
type: [Number, String],
default: undefined,
},
});
const resolvedFixed = computed(() => {
if (props.fixed !== undefined) {
return props.fixed;
}
if (typeof props.fix === 'string') {
return props.fix;
}
return props.fix ? 'right' : false;
});
const hasDynamicShow = computed(() => {
return props.buttons.some((button) => typeof button.show === 'function');
});
const staticVisibleCount = computed(() => {
return props.buttons.filter((button) => button.show !== false).length;
});
const hasMoreStatic = computed(() => {
return staticVisibleCount.value > props.ellipsis + 1;
});
const estimatedButtonCount = computed(() => {
if (staticVisibleCount.value === 0) {
return 0;
}
if (props.ellipsis <= 0) {
return 1;
}
if (hasDynamicShow.value || hasMoreStatic.value) {
return props.ellipsis + 1;
}
return staticVisibleCount.value;
});
const estimatedWidth = computed(() => {
const buttonCount = Math.max(estimatedButtonCount.value, 1);
const buttonsWidth = 24 + buttonCount * 56 + Math.max(buttonCount - 1, 0) * 5;
const minWidth = normalizeWidth(props.minWidth);
if (typeof minWidth === 'number') {
return Math.max(buttonsWidth, minWidth);
}
if (typeof minWidth === 'string') {
const parsed = Number(minWidth.replace('px', ''));
if (!Number.isNaN(parsed)) {
return Math.max(buttonsWidth, parsed);
}
}
return buttonsWidth;
});
const resolvedWidth = computed(() => {
return normalizeWidth(props.width) ?? estimatedWidth.value;
});
const dropdownStyle = computed(() => {
if (props.maxHeight === undefined || props.maxHeight === null || props.maxHeight === '') {
return undefined;
}
const maxHeight = typeof props.maxHeight === 'number' ? `${props.maxHeight}px` : props.maxHeight;
return {
maxHeight,
overflowY: 'auto',
};
});
const getVisibleButtons = (row: any) => {
return props.buttons.filter((button) => resolveMaybeFn(button.show ?? true, row));
};
const hasMore = (row: any) => {
return getVisibleButtons(row).length > props.ellipsis + 1;
};
const getInlineButtons = (row: any) => {
const visibleButtons = getVisibleButtons(row);
if (props.ellipsis <= 0) {
return [];
}
return hasMore(row) ? visibleButtons.slice(0, props.ellipsis) : visibleButtons;
};
const getMoreButtons = (row: any) => {
const visibleButtons = getVisibleButtons(row);
return hasMore(row) ? visibleButtons.slice(props.ellipsis) : [];
};
const isButtonDisabled = (button: FuTableOperationButton, row: any) => {
return Boolean(resolveMaybeFn(button.disabled ?? false, row));
};
const handleButtonClick = (button: FuTableOperationButton, row: any) => {
if (isButtonDisabled(button, row)) {
return;
}
button.click?.(row);
};
const buildButtonKey = (button: FuTableOperationButton) => {
return String(button.label || button.command || button.type || button.icon?.name || 'button');
};
</script>
@@ -0,0 +1,51 @@
<template>
<el-pagination
v-bind="$attrs"
class="fu-table-pagination"
:current-page="currentPage"
:page-size="pageSize"
:page-sizes="pageSizes"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</template>
<script setup lang="ts">
import { type PropType } from 'vue';
defineOptions({ name: 'FuTablePagination' });
const props = defineProps({
currentPage: {
type: Number,
default: 1,
},
pageSize: {
type: Number,
default: 10,
},
pageSizes: {
type: Array as PropType<number[]>,
default: () => [10, 20, 50, 100],
},
total: {
type: Number,
default: 0,
},
});
const emit = defineEmits(['update:pageSize', 'update:currentPage', 'size-change', 'current-change', 'change']);
const handleSizeChange = (pageSize: number) => {
emit('update:pageSize', pageSize);
emit('size-change', pageSize);
emit('change', { currentPage: props.currentPage, pageSize });
};
const handleCurrentChange = (currentPage: number) => {
emit('update:currentPage', currentPage);
emit('current-change', currentPage);
emit('change', { currentPage, pageSize: props.pageSize });
};
</script>
@@ -0,0 +1,3 @@
import FuCompatComponents from '@/components/fu';
void FuCompatComponents;
+31
View File
@@ -0,0 +1,31 @@
import { type App } from 'vue';
import FuInputRwSwitch from './FuInputRwSwitch.vue';
import FuReadWriteSwitch from './FuReadWriteSwitch.vue';
import FuSelectRwSwitch from './FuSelectRwSwitch.vue';
import FuStep from './FuStep';
import FuSteps from './FuSteps';
import FuTable from './FuTable';
import FuTableColumnSelect from './FuTableColumnSelect.vue';
import FuTableOperations from './FuTableOperations.vue';
import FuTablePagination from './FuTablePagination.vue';
const components = [
FuTable,
FuTableOperations,
FuTablePagination,
FuInputRwSwitch,
FuReadWriteSwitch,
FuSelectRwSwitch,
FuSteps,
FuStep,
FuTableColumnSelect,
];
export default {
install(app: App) {
components.forEach((component) => {
app.component((component as any).name, component as any);
});
},
};
+68
View File
@@ -0,0 +1,68 @@
import { Comment, Fragment, Text, type VNode } from 'vue';
export interface FuTableColumnConfig {
key: string;
label: string;
prop?: string;
show: boolean;
fixed?: boolean | string;
}
export interface FuTableOperationButton {
label?: string | number;
click?: (row: any) => void;
disabled?: boolean | ((row: any) => boolean);
show?: boolean | ((row: any) => boolean);
type?: string;
icon?: any;
divided?: boolean;
[key: string]: any;
}
export const FU_TABLE_STORAGE_PREFIX = 'FU-T-';
export const flattenVNodes = (nodes: VNode[] = []): VNode[] => {
const flattened: VNode[] = [];
for (const node of nodes) {
if (!node) {
continue;
}
if (Array.isArray(node)) {
flattened.push(...flattenVNodes(node));
continue;
}
if (node.type === Fragment) {
flattened.push(...flattenVNodes((node.children as VNode[]) || []));
continue;
}
if (node.type === Comment || node.type === Text) {
continue;
}
flattened.push(node);
}
return flattened;
};
export const getVNodeComponentName = (vnode: VNode) => {
const type = vnode.type as any;
return type?.name || type?.__name || type;
};
export const isElTableColumnVNode = (vnode: VNode) => {
const type = vnode.type as any;
return type === 'el-table-column' || type?.name === 'ElTableColumn' || type?.__name === 'ElTableColumn';
};
export const resolveMaybeFn = <T, R>(value: R | ((row: T) => R), row: T): R => {
if (typeof value === 'function') {
return (value as (row: T) => R)(row);
}
return value;
};
export const updateArrayInPlace = <T>(target: T[], next: T[]) => {
if (target.length === next.length && target.every((item, index) => item === next[index])) {
return;
}
target.splice(0, target.length, ...next);
};
+2
View File
@@ -10,8 +10,10 @@ import CopyButton from '@/components/copy-button/index.vue';
import MsgInfo from '@/components/msg-info/index.vue';
import DrawerPro from '@/components/drawer-pro/index.vue';
import DialogPro from '@/components/dialog-pro/index.vue';
import FuComponents from '@/components/fu';
export default {
install(app: App) {
app.use(FuComponents);
app.component(LayoutContent.name, LayoutContent);
app.component(RouterButton.name, RouterButton);
app.component(ComplexTable.name, ComplexTable);
+1 -1
View File
@@ -32,7 +32,7 @@
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { copyText } from '@/utils/util';
import { copyText } from '@/utils/clipboard';
import FileList from '@/components/file-list/index.vue';
const em = defineEmits(['update:tags']);
@@ -75,7 +75,7 @@ import { FitAddon } from '@xterm/addon-fit';
import { Terminal } from '@xterm/xterm';
import '@xterm/xterm/css/xterm.css';
import i18n from '@/lang';
import { dateFormatForName } from '@/utils/util';
import { dateFormatForName } from '@/utils/date';
import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue';
import { ElMessageBox } from 'element-plus';
import { MsgError, MsgSuccess } from '@/utils/message';
+1 -1
View File
@@ -50,7 +50,7 @@
</template>
<script lang="ts" setup>
import { nextTick, onMounted, onUnmounted, reactive, ref, computed } from 'vue';
import { downloadFile } from '@/utils/util';
import { downloadFile } from '@/utils/file';
import { readByLine } from '@/api/modules/files';
import { GlobalStore } from '@/store';
import bus from '@/global/bus';
+1 -1
View File
@@ -19,7 +19,7 @@ import { ref } from 'vue';
import { getAgentSettingInfo } from '@/api/modules/setting';
import i18n from '@/lang';
import { MsgError, MsgWarning } from '@/utils/message';
import { jumpToPath } from '@/utils/util';
import { jumpToPath } from '@/utils/router';
import { useRouter } from 'vue-router';
import { GlobalStore } from '@/store';
const globalStore = GlobalStore();
+1 -1
View File
@@ -50,7 +50,7 @@
import TaskLog from '@/components/log/task/index.vue';
import NodeSelect from '@/components/node-select/index.vue';
import { dateFormat } from '@/utils/util';
import { dateFormat } from '@/utils/date';
import { searchTasks } from '@/api/modules/log';
import { reactive, ref } from 'vue';
import { Log } from '@/api/interface/log';
+3 -1
View File
@@ -135,7 +135,9 @@
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { computeSize, newUUID, transferTimeToSecond } from '@/utils/util';
import { computeSize } from '@/utils/size';
import { newUUID } from '@/utils/id';
import { transferTimeToSecond } from '@/utils/validate';
import i18n from '@/lang';
import { UploadFile, UploadFiles, UploadInstance, UploadProps, UploadRawFile, genFileId } from 'element-plus';
import { File } from '@/api/interface/file';
@@ -5,7 +5,7 @@
import { onMounted, nextTick, watch, onBeforeUnmount, ref } from 'vue';
import echarts from '@/utils/echarts';
import { GlobalStore } from '@/store';
import { computeSizeFromKBs, computeSizeFromKB, computeSizeFromMB } from '@/utils/util';
import { computeSizeFromKBs, computeSizeFromKB, computeSizeFromMB } from '@/utils/size';
import i18n from '@/lang';
const globalStore = GlobalStore();
const isDarkTheme = ref(false);
+148
View File
@@ -0,0 +1,148 @@
type FuLocaleMessage = Record<string, unknown>;
const fuLocales: Record<string, FuLocaleMessage> = {
en: {
fu: {
table: {
more: 'More',
custom_table_rows: 'Custom columns',
},
steps: {
cancel: 'Cancel',
prev: 'Previous',
next: 'Next',
finish: 'Finish',
},
},
},
'es-ES': {
fu: {
table: {
more: 'Más',
custom_table_rows: 'Columnas personalizadas',
},
steps: {
cancel: 'Cancelar',
prev: 'Anterior',
next: 'Siguiente',
finish: 'Finalizar',
},
},
},
ja: {
fu: {
table: {
more: 'もっと',
custom_table_rows: 'カスタム列',
},
steps: {
cancel: 'キャンセル',
prev: '前へ',
next: '次へ',
finish: '完了',
},
},
},
ko: {
fu: {
table: {
more: '더보기',
custom_table_rows: '사용자 지정 ',
},
steps: {
cancel: '취소',
prev: '이전',
next: '다음',
finish: '완료',
},
},
},
ms: {
fu: {
table: {
more: 'Lagi',
custom_table_rows: 'Lajur tersuai',
},
steps: {
cancel: 'Batal',
prev: 'Sebelumnya',
next: 'Seterusnya',
finish: 'Selesai',
},
},
},
'pt-BR': {
fu: {
table: {
more: 'Mais',
custom_table_rows: 'Colunas personalizadas',
},
steps: {
cancel: 'Cancelar',
prev: 'Anterior',
next: 'Próximo',
finish: 'Concluir',
},
},
},
ru: {
fu: {
table: {
more: 'Еще',
custom_table_rows: 'Настройка колонок',
},
steps: {
cancel: 'Отмена',
prev: 'Назад',
next: 'Далее',
finish: 'Готово',
},
},
},
tr: {
fu: {
table: {
more: 'Daha fazla',
custom_table_rows: 'Özel sütunlar',
},
steps: {
cancel: 'İptal',
prev: 'Önceki',
next: 'Sonraki',
finish: 'Tamamla',
},
},
},
zh: {
fu: {
table: {
more: '更多',
custom_table_rows: '自定义列',
},
steps: {
cancel: '取消',
prev: '上一步',
next: '下一步',
finish: '完成',
},
},
},
'zh-Hant': {
fu: {
table: {
more: '更多',
custom_table_rows: '自訂欄位',
},
steps: {
cancel: '取消',
prev: '上一步',
next: '下一步',
finish: '完成',
},
},
},
};
export const getFuLocaleMessage = (locale: string) => {
return fuLocales[locale] || fuLocales.en;
};
+2 -2
View File
@@ -1,4 +1,4 @@
import fit2cloudEnLocale from 'fit2cloud-ui-plus/src/locale/lang/en';
import { getFuLocaleMessage } from '@/lang/fu';
const message = {
commons: {
@@ -4468,6 +4468,6 @@ const message = {
};
export default {
...fit2cloudEnLocale,
...getFuLocaleMessage('en'),
...message,
};
+2 -2
View File
@@ -1,4 +1,4 @@
import fit2cloudEsEsLocale from 'fit2cloud-ui-plus/src/locale/lang/en';
import { getFuLocaleMessage } from '@/lang/fu';
const message = {
commons: {
@@ -4413,6 +4413,6 @@ const message = {
};
export default {
...fit2cloudEsEsLocale,
...getFuLocaleMessage('es-ES'),
...message,
};
+2 -2
View File
@@ -1,4 +1,4 @@
import fit2cloudEnLocale from 'fit2cloud-ui-plus/src/locale/lang/ja';
import { getFuLocaleMessage } from '@/lang/fu';
const message = {
commons: {
@@ -4409,6 +4409,6 @@ const message = {
},
};
export default {
...fit2cloudEnLocale,
...getFuLocaleMessage('ja'),
...message,
};
+2 -2
View File
@@ -1,4 +1,4 @@
import fit2cloudKoLocale from 'fit2cloud-ui-plus/src/locale/lang/en';
import { getFuLocaleMessage } from '@/lang/fu';
const message = {
commons: {
@@ -4306,6 +4306,6 @@ const message = {
};
export default {
...fit2cloudKoLocale,
...getFuLocaleMessage('ko'),
...message,
};
+2 -2
View File
@@ -1,4 +1,4 @@
import fit2cloudEnLocale from 'fit2cloud-ui-plus/src/locale/lang/ms';
import { getFuLocaleMessage } from '@/lang/fu';
const message = {
commons: {
@@ -4465,6 +4465,6 @@ const message = {
};
export default {
...fit2cloudEnLocale,
...getFuLocaleMessage('ms'),
...message,
};
+2 -2
View File
@@ -1,4 +1,4 @@
import fit2cloudPtBrLocale from 'fit2cloud-ui-plus/src/locale/lang/pt-br';
import { getFuLocaleMessage } from '@/lang/fu';
const message = {
commons: {
@@ -4617,6 +4617,6 @@ const message = {
};
export default {
...fit2cloudPtBrLocale,
...getFuLocaleMessage('pt-BR'),
...message,
};
+2 -2
View File
@@ -1,4 +1,4 @@
import fit2cloudEnLocale from 'fit2cloud-ui-plus/src/locale/lang/ru';
import { getFuLocaleMessage } from '@/lang/fu';
const message = {
commons: {
@@ -4474,6 +4474,6 @@ const message = {
};
export default {
...fit2cloudEnLocale,
...getFuLocaleMessage('ru'),
...message,
};
+2 -2
View File
@@ -1,4 +1,4 @@
import fit2cloudEnLocale from 'fit2cloud-ui-plus/src/locale/lang/en';
import { getFuLocaleMessage } from '@/lang/fu';
const message = {
commons: {
@@ -4463,6 +4463,6 @@ const message = {
};
export default {
...fit2cloudEnLocale,
...getFuLocaleMessage('tr'),
...message,
};
+2 -2
View File
@@ -1,4 +1,4 @@
import fit2cloudTwLocale from 'fit2cloud-ui-plus/src/locale/lang/zh-tw';
import { getFuLocaleMessage } from '@/lang/fu';
const message = {
commons: {
@@ -4067,6 +4067,6 @@ const message = {
},
};
export default {
...fit2cloudTwLocale,
...getFuLocaleMessage('zh-Hant'),
...message,
};
+2 -2
View File
@@ -1,4 +1,4 @@
import fit2cloudZhLocale from 'fit2cloud-ui-plus/src/locale/lang/zh-cn';
import { getFuLocaleMessage } from '@/lang/fu';
const message = {
commons: {
@@ -4130,6 +4130,6 @@ const message = {
},
};
export default {
...fit2cloudZhLocale,
...getFuLocaleMessage('zh'),
...message,
};
+5 -7
View File
@@ -1,6 +1,10 @@
import { computed, createApp } from 'vue';
import { createApp } from 'vue';
import App from './App.vue';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import * as Icons from '@element-plus/icons-vue';
import '@/styles/index.scss';
import '@/styles/common.scss';
import '@/assets/iconfont/iconfont.css';
@@ -16,10 +20,6 @@ import pinia from '@/store/index';
import SvgIcon from './components/svg-icon/svg-icon.vue';
import Components from '@/components';
import ElementPlus from 'element-plus';
import Fit2CloudPlus from 'fit2cloud-ui-plus';
import * as Icons from '@element-plus/icons-vue';
import directives from '@/directives/index';
const bootstrap = async () => {
@@ -28,10 +28,8 @@ const bootstrap = async () => {
await Promise.all([loadLocaleMessages(currentLocale), ensureFallbackLocale()]);
const app = createApp(App);
const fit2cloudLocale = computed(() => i18n.global.getLocaleMessage(i18n.global.locale.value));
app.component('SvgIcon', SvgIcon);
app.use(ElementPlus);
app.use(Fit2CloudPlus, { locale: fit2cloudLocale });
Object.keys(Icons).forEach((key) => {
app.component(key, Icons[key as keyof typeof Icons]);
+177
View File
@@ -0,0 +1,177 @@
.fu-table {
width: 100%;
}
.fu-table-header {
font-weight: 600;
}
.fu-table-operations {
display: inline-flex;
align-items: center;
gap: 5px;
justify-content: flex-start;
max-width: 100%;
vertical-align: middle;
flex-wrap: nowrap;
}
.fu-table-operations__action {
display: inline-flex;
align-items: center;
justify-content: flex-start;
flex: 0 0 auto;
}
.fu-table-operations__button {
display: inline-flex;
align-items: center;
justify-content: flex-start;
min-width: 0;
padding-inline: 0;
white-space: nowrap;
}
.fu-table-more-button {
display: inline-flex;
}
.fu-table-operations__dropdown-trigger {
display: inline-flex;
align-items: center;
justify-content: flex-start;
white-space: nowrap;
}
.fu-search-bar {
display: inline-flex;
align-items: center;
}
.fu-search-bar-button {
padding-inline: 8px;
}
.fu-table-column-select__panel {
min-width: 220px;
}
.fu-table-column-select__title {
font-size: 14px;
font-weight: 600;
margin-bottom: 12px;
}
.fu-table-column-select__list {
display: flex;
flex-direction: column;
gap: 8px;
}
.fu-table-column-select__item {
display: flex;
align-items: center;
gap: 8px;
}
.fu-table-column-select__drag {
cursor: move;
color: var(--el-text-color-secondary);
}
.fu-read-write-switch,
.fu-input-rw-switch,
.fu-select-rw-switch {
width: 100%;
min-height: 32px;
}
.fu-read-write-switch__read,
.fu-input-rw-switch__read,
.fu-select-rw-switch__read {
width: 100%;
min-height: 32px;
display: inline-flex;
align-items: center;
cursor: pointer;
}
.fu-read-write-switch__text,
.fu-input-rw-switch__text,
.fu-select-rw-switch__text {
display: inline-block;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.fu-steps {
display: flex;
gap: 24px;
}
.fu-steps--vertical {
align-items: flex-start;
}
.fu-steps__nav {
display: flex;
}
.fu-steps--vertical .fu-steps__nav {
flex-direction: column;
min-width: 180px;
}
.fu-steps__item {
width: 100%;
display: flex;
align-items: center;
gap: 12px;
border: 0;
background: transparent;
padding: 0;
color: var(--el-text-color-regular);
cursor: pointer;
text-align: left;
}
.fu-steps__item.is-active {
color: var(--el-color-primary);
font-weight: 600;
}
.fu-steps__item.is-finished .fu-steps__index {
background: var(--el-color-success);
color: var(--el-color-white);
}
.fu-steps__item:disabled {
cursor: not-allowed;
}
.fu-steps__index {
width: 28px;
height: 28px;
border-radius: 999px;
border: 1px solid var(--el-border-color);
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.fu-steps__title {
line-height: 1.4;
}
.fu-steps__content {
flex: 1;
min-width: 0;
}
.fu-steps__footer {
width: 100%;
margin-top: 16px;
}
+1 -1
View File
@@ -1,6 +1,6 @@
@use 'fit2cloud-ui-plus/src/styles/index.scss' as *;
@use './element.scss';
@use './element-dark.scss';
@use './fu.scss';
@use './moblie.scss';
@use './reset.scss';
@use './var.scss';
+55
View File
@@ -0,0 +1,55 @@
import i18n from '@/lang';
export function getLanguage() {
return localStorage.getItem('lang') || 'zh';
}
function normalizeAppLocaleKey(language: string) {
const localeMap: Record<string, string> = {
tw: 'zh-hant',
'zh-Hant': 'zh-hant',
'pt-BR': 'pt-br',
'es-ES': 'es-es',
};
return localeMap[language] || language.toLowerCase();
}
export function getLabel(row: any) {
const language = localStorage.getItem('lang') || 'zh';
const lang = normalizeAppLocaleKey(language);
if (row.label && typeof row.label[lang] === 'string' && row.label[lang] !== '') {
return row.label[lang];
}
if (language == 'zh' || language == 'tw') {
return row.labelZh;
}
return row.labelEn;
}
export function getDescription(row: any) {
const language = localStorage.getItem('lang') || 'zh';
const lang = normalizeAppLocaleKey(language);
if (row.description && typeof row.description[lang] === 'string' && row.description[lang] !== '') {
return row.description[lang];
}
return '';
}
export function getDBName(length: number): string {
const chars = 'abcdefhijkmnprstwxyz2345678';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
export function getRuntimeLabel(type: string) {
if (type == 'appstore') {
return i18n.global.t('menu.apps');
}
if (type == 'local') {
return i18n.global.t('commons.table.local');
}
return '';
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { jumpToPath } from './util';
import { jumpToPath } from './router';
import router from '@/routers';
import { GlobalStore } from '@/store';
+83
View File
@@ -0,0 +1,83 @@
import CryptoJS from 'crypto-js';
import JSEncrypt from 'jsencrypt';
export function getCookie(name: string) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop()?.split(';').shift();
}
function rsaEncrypt(data: string, publicKey: string) {
if (!data) {
return data;
}
const jsEncrypt = new JSEncrypt();
jsEncrypt.setPublicKey(publicKey);
return jsEncrypt.encrypt(data);
}
function aesEncrypt(data: string, key: string) {
const keyBytes = CryptoJS.enc.Utf8.parse(key);
const iv = CryptoJS.lib.WordArray.random(16);
const encrypted = CryptoJS.AES.encrypt(data, keyBytes, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
});
return iv.toString(CryptoJS.enc.Base64) + ':' + encrypted.toString();
}
function urlDecode(value: string): string {
return decodeURIComponent(value.replace(/\+/g, ' '));
}
function generateAESKey(): string {
const keyLength = 16;
const randomBytes = new Uint8Array(keyLength);
crypto.getRandomValues(randomBytes);
return Array.from(randomBytes)
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
}
export const encryptPassword = (password: string) => {
if (!password) {
return '';
}
let rsaPublicKeyText = getCookie('panel_public_key');
if (!rsaPublicKeyText) {
return password;
}
rsaPublicKeyText = urlDecode(rsaPublicKeyText);
const aesKey = generateAESKey();
rsaPublicKeyText = rsaPublicKeyText.replaceAll('"', '');
const rsaPublicKey = atob(rsaPublicKeyText);
const keyCipher = rsaEncrypt(aesKey, rsaPublicKey);
const passwordCipher = aesEncrypt(password, aesKey);
return `${keyCipher}:${passwordCipher}`;
};
export function base64UrlToBuffer(base64url: string): ArrayBuffer {
let base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');
const pad = base64.length % 4;
if (pad) {
base64 += '='.repeat(4 - pad);
}
const binary = window.atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes.buffer;
}
export function bufferToBase64Url(buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer);
let binary = '';
bytes.forEach((byte) => {
binary += String.fromCharCode(byte);
});
const base64 = window.btoa(binary);
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
+24
View File
@@ -0,0 +1,24 @@
export const toLink = (link: string) => {
const ipv6Regex = /^https?:\/\/([a-f0-9:]+):(\d+)(\/?.*)?$/i;
try {
if (ipv6Regex.test(link)) {
const match = link.match(ipv6Regex);
if (match) {
const ipv6 = match[1];
const port = match[2];
const path = match[3] || '';
link = `${link.startsWith('https') ? 'https' : 'http'}://[${ipv6}]:${port}${path}`;
}
}
window.open(link, '_blank');
} catch (e) {}
};
export const preloadImage = (url: string): Promise<string> => {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve(url);
img.onerror = () => resolve(url);
img.src = url;
});
};
+15
View File
@@ -0,0 +1,15 @@
import i18n from '@/lang';
import useClipboard from 'vue-clipboard3';
import { MsgError, MsgSuccess } from '@/utils/message';
const { toClipboard } = useClipboard();
export async function copyText(content: string) {
try {
await toClipboard(content);
MsgSuccess(i18n.global.t('commons.msg.copySuccess'));
} catch (e) {
MsgError(i18n.global.t('commons.msg.copyFailed'));
}
}
+172
View File
@@ -0,0 +1,172 @@
import i18n from '@/lang';
export function loadUpTime(timeSince: string) {
if (!timeSince) {
return '';
}
const targetTime = new Date(timeSince);
const currentTime = new Date();
const uptime = (currentTime.getTime() - targetTime.getTime()) / 1000;
if (uptime <= 0) {
return '';
}
let days = Math.floor(uptime / 86400);
let hours = Math.floor((uptime % 86400) / 3600);
let minutes = Math.floor((uptime % 3600) / 60);
let seconds = Math.floor(uptime % 60);
if (days !== 0) {
return (
days +
i18n.global.t('commons.units.day') +
' ' +
hours +
i18n.global.t('commons.units.hour') +
' ' +
minutes +
i18n.global.t('commons.units.minute') +
' ' +
seconds +
i18n.global.t('commons.units.second')
);
}
if (hours !== 0) {
return (
hours +
i18n.global.t('commons.units.hour') +
' ' +
minutes +
i18n.global.t('commons.units.minute') +
' ' +
seconds +
i18n.global.t('commons.units.second')
);
}
if (minutes !== 0) {
return minutes + i18n.global.t('commons.units.minute') + ' ' + seconds + i18n.global.t('commons.units.second');
}
return seconds + i18n.global.t('commons.units.second');
}
export function getCurrentDateFormatted() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
return `${year}${month}${day}${hours}${minutes}${seconds}`;
}
export function dateFormat(row: any, col: any, dataStr: any) {
const date = new Date(dataStr);
const y = date.getFullYear();
let m: string | number = date.getMonth() + 1;
m = m < 10 ? `0${String(m)}` : m;
let d: string | number = date.getDate();
d = d < 10 ? `0${String(d)}` : d;
let h: string | number = date.getHours();
h = h < 10 ? `0${String(h)}` : h;
let minute: string | number = date.getMinutes();
minute = minute < 10 ? `0${String(minute)}` : minute;
let second: string | number = date.getSeconds();
second = second < 10 ? `0${String(second)}` : second;
return `${String(y)}-${String(m)}-${String(d)} ${String(h)}:${String(minute)}:${String(second)}`;
}
export function dateFormatSimple(dataStr: any) {
const date = new Date(dataStr);
const y = date.getFullYear();
let m: string | number = date.getMonth() + 1;
m = m < 10 ? `0${String(m)}` : m;
let d: string | number = date.getDate();
d = d < 10 ? `0${String(d)}` : d;
return `${String(y)}-${String(m)}-${String(d)}`;
}
export function dateFormatForName(dataStr: any) {
const date = new Date(dataStr);
const y = date.getFullYear();
let m: string | number = date.getMonth() + 1;
m = m < 10 ? `0${String(m)}` : m;
let d: string | number = date.getDate();
d = d < 10 ? `0${String(d)}` : d;
let h: string | number = date.getHours();
h = h < 10 ? `0${String(h)}` : h;
let minute: string | number = date.getMinutes();
minute = minute < 10 ? `0${String(minute)}` : minute;
let second: string | number = date.getSeconds();
second = second < 10 ? `0${String(second)}` : second;
return `${String(y)}${String(m)}${String(d)}${String(h)}${String(minute)}${String(second)}`;
}
export function dateFormatWithoutYear(dataStr: any) {
const date = new Date(dataStr);
let m: string | number = date.getMonth() + 1;
m = m < 10 ? `0${String(m)}` : m;
let d: string | number = date.getDate();
d = d < 10 ? `0${String(d)}` : d;
let h: string | number = date.getHours();
h = h < 10 ? `0${String(h)}` : h;
let minute: string | number = date.getMinutes();
minute = minute < 10 ? `0${String(minute)}` : minute;
let s: string | number = date.getSeconds();
s = s < 10 ? `0${String(s)}` : s;
return `${String(m)}-${String(d)}\n${String(h)}:${String(minute)}:${String(s)}`;
}
export function dateFormatForSecond(dataStr: any) {
const date = new Date(dataStr);
let h: string | number = date.getHours();
h = h < 10 ? `0${String(h)}` : h;
let minute: string | number = date.getMinutes();
minute = minute < 10 ? `0${String(minute)}` : minute;
let second: string | number = date.getSeconds();
second = second < 10 ? `0${String(second)}` : second;
return `${String(h)}:${String(minute)}:${String(second)}`;
}
export function loadZero(i: number) {
return i < 10 ? '0' + i : '' + i;
}
export function getAge(d1: string): string {
const dateBegin = new Date(d1);
const dateEnd = new Date();
const dateDiff = dateEnd.getTime() - dateBegin.getTime();
const dayDiff = Math.floor(dateDiff / (24 * 3600 * 1000));
const leave1 = dateDiff % (24 * 3600 * 1000);
const hours = Math.floor(leave1 / (3600 * 1000));
const leave2 = leave1 % (3600 * 1000);
const minutes = Math.floor(leave2 / (60 * 1000));
let res = '';
if (dayDiff > 0) {
res += String(dayDiff) + ' ' + i18n.global.t('commons.units.day', dayDiff) + ' ';
if (hours <= 0) {
return res;
}
}
if (hours > 0) {
res += String(hours) + ' ' + i18n.global.t('commons.units.hour', hours);
return res;
}
if (minutes > 0) {
res += String(minutes) + ' ' + i18n.global.t('commons.units.minute', minutes);
return res;
}
return i18n.global.t('app.less1Minute');
}
export function getDateStr() {
let now: Date = new Date();
let year: number = now.getFullYear();
let month: number = now.getMonth() + 1;
let date: number = now.getDate();
let hours: number = now.getHours();
let minutes: number = now.getMinutes();
let seconds: number = now.getSeconds();
return `${year}-${month < 10 ? '0' + month : month}-${date < 10 ? '0' + date : date}-${
hours < 10 ? '0' + hours : hours
}-${minutes < 10 ? '0' + minutes : minutes}-${seconds < 10 ? '0' + seconds : seconds}`;
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { isJson } from './util';
import { isJson } from './misc';
export function formatImageStdout(stdout: string) {
let lines = stdout.split('\r\n');
+154
View File
@@ -0,0 +1,154 @@
const icons = new Map([
['.zip', 'p-file-zip'],
['.gz', 'p-file-zip'],
['.tar.bz2', 'p-file-zip'],
['.bz2', 'p-file-zip'],
['.xz', 'p-file-zip'],
['.tar.xz', 'p-file-zip'],
['.tar', 'p-file-zip'],
['.tar.gz', 'p-file-zip'],
['.war', 'p-file-zip'],
['.tgz', 'p-file-zip'],
['.7z', 'p-file-zip'],
['.rar', 'p-file-zip'],
['.mp3', 'p-file-mp3'],
['.svg', 'p-file-svg'],
['.txt', 'p-file-txt'],
['.html', 'p-file-html'],
['.word', 'p-file-word'],
['.ppt', 'p-file-ppt'],
['.jpg', 'p-file-jpg'],
['.jpeg', 'p-file-jpg'],
['.png', 'p-file-png'],
['.xlsx', 'p-file-excel'],
['.doc', 'p-file-word'],
['.xls', 'p-file-excel'],
['.docx', 'p-file-word'],
['.pdf', 'p-file-pdf'],
['.bmp', 'p-file-png'],
['.gif', 'p-file-png'],
['.tiff', 'p-file-png'],
['.ico', 'p-file-png'],
['.webp', 'p-file-png'],
['.mp4', 'p-file-video'],
['.webm', 'p-file-video'],
['.mov', 'p-file-video'],
['.wmv', 'p-file-video'],
['.mkv', 'p-file-video'],
['.avi', 'p-file-video'],
['.wma', 'p-file-video'],
['.flv', 'p-file-video'],
['.wav', 'p-file-mp3'],
['.wma', 'p-file-mp3'],
['.ape', 'p-file-mp3'],
['.acc', 'p-file-mp3'],
['.ogg', 'p-file-mp3'],
['.flac', 'p-file-mp3'],
]);
const fileTypes = {
image: ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff', '.ico', '.svg', '.webp'],
compress: ['.zip', '.rar', '.gz', '.war', '.tgz', '.7z', '.tar.gz', '.tar', '.bz2', '.xz', '.tar.bz2', '.tar.xz'],
video: ['.mp4', '.webm', '.mov', '.wmv', '.mkv', '.avi', '.wma', '.flv'],
audio: ['.mp3', '.wav', '.wma', '.ape', '.acc', '.ogg', '.flac'],
pdf: ['.pdf'],
word: ['.doc', '.docx'],
excel: ['.xls', '.xlsx'],
text: ['.iso', '.tiff', '.exe', '.so', '.bz', '.dmg', '.apk', '.pptx', '.ppt', '.xlsb'],
};
export function getIcon(extension: string): string {
if (icons.get(extension) != undefined) {
return String(icons.get(extension));
}
return 'p-file-normal';
}
export const getFileType = (extension: string) => {
let type = 'text';
Object.entries(fileTypes).forEach(([key, extensions]) => {
if (extensions.includes(extension.toLowerCase())) {
type = key;
}
});
return type;
};
const convertTypes = ['image', 'video', 'audio'] as const;
type ConvertType = (typeof convertTypes)[number];
export function isConvertible(extension: string, mimeType: string): boolean {
return convertTypes.includes(getFileType(extension) as ConvertType) && /^(image|audio|video)\//.test(mimeType);
}
export const isSensitiveLinuxPath = (path: string) => {
const sensitivePath = [
'/',
'/bin',
'/sbin',
'/usr/bin',
'/usr/sbin',
'/usr/local/bin',
'/etc',
'/lib',
'/lib64',
'/usr/lib',
'/home',
'/tmp',
'/var',
'/dev',
'/proc',
'/sys',
];
return sensitivePath.indexOf(path) !== -1;
};
function buildFileDownloadUrl(filePath: string, currentNode: string): string {
const base = `${import.meta.env.VITE_API_URL as string}/files/download?operateNode=${currentNode}&`;
return base + 'path=' + encodeURIComponent(filePath);
}
export function buildFileSharePageUrl(code: string, currentNode: string): string {
const shareUrl = new URL(`/s/${encodeURIComponent(code)}`, window.location.origin);
shareUrl.searchParams.set('operateNode', currentNode);
return shareUrl.toString();
}
export function buildFileShareDownloadUrl(code: string, currentNode: string, password?: string): string {
const apiBase = import.meta.env.VITE_API_URL as string;
const normalizedBase = apiBase.replace(/\/$/, '');
const shareUrl = /^https?:\/\//i.test(normalizedBase)
? new URL(`${normalizedBase}/files/share/download`)
: new URL(`${normalizedBase}/files/share/download`, window.location.origin);
shareUrl.searchParams.set('operateNode', currentNode);
shareUrl.searchParams.set('code', code);
if (password && password.length > 0) {
shareUrl.searchParams.set('password', password);
}
return shareUrl.toString();
}
export function buildFileShareQrCodeUrl(code: string, currentNode: string): string {
const apiBase = import.meta.env.VITE_API_URL as string;
const normalizedBase = apiBase.replace(/\/$/, '');
const shareUrl = /^https?:\/\//i.test(normalizedBase)
? new URL(`${normalizedBase}/files/share/qrcode`)
: new URL(`${normalizedBase}/files/share/qrcode`, window.location.origin);
shareUrl.searchParams.set('operateNode', currentNode);
shareUrl.searchParams.set('code', code);
return shareUrl.toString();
}
export function downloadFile(filePath: string, currentNode: string) {
window.open(buildFileDownloadUrl(filePath, currentNode), '_blank');
}
export function downloadWithContent(content: string, fileName: string) {
const downloadUrl = window.URL.createObjectURL(new Blob([content]));
const a = document.createElement('a');
a.style.display = 'none';
a.href = downloadUrl;
a.download = fileName;
const event = new MouseEvent('click');
a.dispatchEvent(event);
}
+14
View File
@@ -0,0 +1,14 @@
import { v4 as uuidv4 } from 'uuid';
export function getRandomStr(length: number): string {
const chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
export const newUUID = () => {
return uuidv4();
};
+79
View File
@@ -0,0 +1,79 @@
import { toUnicode } from 'punycode';
export function deepCopy<T>(obj: any): T {
let newObj: any;
try {
newObj = obj.push ? [] : {};
} catch (error) {
newObj = {};
}
for (let attr in obj) {
if (typeof obj[attr] === 'object') {
newObj[attr] = deepCopy(obj[attr]);
} else {
newObj[attr] = obj[attr];
}
}
return newObj;
}
export function debounce(func: Function, wait: number) {
let timeout: NodeJS.Timeout | null = null;
return function executedFunction(...args: any[]) {
const later = () => {
if (timeout) clearTimeout(timeout);
func(...args);
};
if (timeout) clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
export function isJson(str: string) {
try {
if (typeof JSON.parse(str) === 'object') {
return true;
}
} catch {
return false;
}
}
function compareById(a: any, b: any) {
return a.sort - b.sort;
}
export function sortMenu(arr: any[]) {
if (!arr || arr.length === 0) {
return;
}
arr.forEach((item) => {
if (item.children && Array.isArray(item.children)) {
item.children.sort(compareById);
}
});
arr.sort(compareById);
}
export function isPunycoded(domain: string): boolean {
return domain.includes('xn--');
}
export function GetPunyCodeDomain(domain: string): string {
if (!domain || typeof domain !== 'string') {
return '';
}
const lowerDomain = domain.toLowerCase();
if (!lowerDomain.includes('xn--')) {
return '';
}
try {
const decoded = toUnicode(domain);
if (decoded === domain) {
return '';
}
return decoded;
} catch (error) {
return '';
}
}
+4
View File
@@ -26,6 +26,10 @@ export const routerToPathWithQuery = async (path: string, query: any) => {
tabStoreMiddleWare();
};
export const jumpToPath = (router: any, path: string) => {
routerToPathWithQuery(path, { uncached: 'true' });
};
export const routerToNameWithParams = async (name: string, params: any) => {
await router.push({ name: name, params: params });
tabStoreMiddleWare();
+81
View File
@@ -0,0 +1,81 @@
function formattedNumber(num: string) {
return num.endsWith('.00') ? Number(num.slice(0, -3)) : Number(num);
}
export function computeSize(size: number): string {
const num = 1024.0;
if (size < num) return size + ' B';
if (size < Math.pow(num, 2)) return formattedNumber((size / num).toFixed(2)) + ' KB';
if (size < Math.pow(num, 3)) return formattedNumber((size / Math.pow(num, 2)).toFixed(2)) + ' MB';
if (size < Math.pow(num, 4)) return formattedNumber((size / Math.pow(num, 3)).toFixed(2)) + ' GB';
return formattedNumber((size / Math.pow(num, 4)).toFixed(2)) + ' TB';
}
export function computeSizeForDocker(size: number): string {
const num = 1024.0;
if (size < num) return size + ' B';
if (size < Math.pow(num, 2)) return formattedNumber((size / num).toFixed(2)) + ' KiB';
if (size < Math.pow(num, 3)) return formattedNumber((size / Math.pow(num, 2)).toFixed(2)) + ' MiB';
if (size < Math.pow(num, 4)) return formattedNumber((size / Math.pow(num, 3)).toFixed(2)) + ' GiB';
return formattedNumber((size / Math.pow(num, 4)).toFixed(2)) + ' TiB';
}
export function computeSize2(size: number): string {
const num = 1000.0;
if (size < num) return size + ' B';
if (size < Math.pow(num, 2)) return formattedNumber((size / num).toFixed(2)) + ' KB';
if (size < Math.pow(num, 3)) return formattedNumber((size / Math.pow(num, 2)).toFixed(2)) + ' MB';
if (size < Math.pow(num, 4)) return formattedNumber((size / Math.pow(num, 3)).toFixed(2)) + ' GB';
return formattedNumber((size / Math.pow(num, 4)).toFixed(2)) + ' TB';
}
export function computeCPU(size: number): string {
const num = 1000;
if (size < num) return size + ' ns';
if (size < Math.pow(num, 2)) return formattedNumber((size / num).toFixed(2)) + ' μs';
if (size < Math.pow(num, 3)) return formattedNumber((size / Math.pow(num, 2)).toFixed(2)) + ' ms';
return formattedNumber((size / Math.pow(num, 3)).toFixed(2)) + ' s';
}
export function splitSize(size: number): any {
const num = 1024.0;
if (size < num) return { size: Number(size), unit: 'B' };
if (size < Math.pow(num, 2)) return { size: formattedNumber((size / num).toFixed(2)), unit: 'KB' };
if (size < Math.pow(num, 3))
return { size: formattedNumber((size / Number(Math.pow(num, 2).toFixed(2))).toFixed(2)), unit: 'MB' };
if (size < Math.pow(num, 4))
return { size: formattedNumber((size / Number(Math.pow(num, 3))).toFixed(2)), unit: 'GB' };
return { size: formattedNumber((size / Number(Math.pow(num, 4))).toFixed(2)), unit: 'TB' };
}
export function computeSizeFromMB(size: number): string {
const num = 1024.0;
if (size < num) return size + ' MB';
if (size < Math.pow(num, 2)) return (size / num).toFixed(2) + ' GB';
return (size / Math.pow(num, 3)).toFixed(2) + ' TB';
}
export function computeSizeFromKB(size: number): string {
const num = 1024.0;
if (size < num) return size + ' KB';
if (size < Math.pow(num, 2)) return (size / num).toFixed(2) + ' MB';
if (size < Math.pow(num, 3)) return (size / Math.pow(num, 2)).toFixed(2) + ' GB';
return (size / Math.pow(num, 3)).toFixed(2) + ' TB';
}
export function computeSizeFromByte(size: number): string {
const num = 1024.0;
if (size < num) return size + ' B';
if (size < Math.pow(num, 2)) return (size / num).toFixed(2) + ' KB';
if (size < Math.pow(num, 3)) return (size / Math.pow(num, 2)).toFixed(2) + ' MB';
if (size < Math.pow(num, 4)) return (size / Math.pow(num, 2)).toFixed(2) + ' GB';
return (size / Math.pow(num, 5)).toFixed(2) + ' TB';
}
export function computeSizeFromKBs(size: number): string {
const num = 1024.0;
if (size < num) return size + ' KB/s';
if (size < Math.pow(num, 2)) return (size / num).toFixed(2) + ' MB/s';
if (size < Math.pow(num, 3)) return (size / Math.pow(num, 2)).toFixed(2) + ' GB/s';
return (size / Math.pow(num, 3)).toFixed(2) + ' TB/s';
}
+46
View File
@@ -0,0 +1,46 @@
import { AcmeAccountTypes, DNSTypes, KeyTypes } from '@/global/mimetype';
import i18n from '@/lang';
export function getAccountName(type: string) {
for (const item of AcmeAccountTypes) {
if (item.value === type) {
return item.label;
}
}
return '';
}
export function getKeyName(type: string) {
for (const item of KeyTypes) {
if (item.value === type) {
return item.label;
}
}
return '';
}
export function getDNSName(type: string) {
for (const item of DNSTypes) {
if (item.value === type) {
return item.label;
}
}
return '';
}
export function getProvider(provider: string): string {
switch (provider) {
case 'dnsAccount':
return i18n.global.t('website.dnsAccount');
case 'dnsManual':
return i18n.global.t('website.dnsManual');
case 'http':
return 'HTTP';
case 'selfSigned':
return i18n.global.t('ssl.selfSigned');
case 'fromMaster':
return i18n.global.t('ssl.fromMaster');
default:
return i18n.global.t('ssl.manualCreate');
}
}
File diff suppressed because it is too large Load Diff
+184
View File
@@ -0,0 +1,184 @@
import i18n from '@/lang';
export function checkIp(value: string): boolean {
if (value === '') {
return true;
}
const reg =
/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;
return !reg.test(value) && value !== '';
}
export function checkDomain(value: string): boolean {
if (value === '') {
return true;
}
const reg = /^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/;
return !reg.test(value) && value !== '';
}
export function isDomain(value: string): boolean {
if (value === '') {
return true;
}
const reg = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i;
return value !== '' && reg.test(value);
}
export function checkIpV4V6(value: string): boolean {
if (value === '') {
return true;
}
const IPv4SegmentFormat = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])';
const IPv4AddressFormat = `(${IPv4SegmentFormat}[.]){3}${IPv4SegmentFormat}`;
const IPv4AddressRegExp = new RegExp(`^${IPv4AddressFormat}$`);
const IPv6SegmentFormat = '(?:[0-9a-fA-F]{1,4})';
const IPv6AddressRegExp = new RegExp(
'^(' +
`(?:${IPv6SegmentFormat}:){7}(?:${IPv6SegmentFormat}|:)|` +
`(?:${IPv6SegmentFormat}:){6}(?:${IPv4AddressFormat}|:${IPv6SegmentFormat}|:)|` +
`(?:${IPv6SegmentFormat}:){5}(?::${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,2}|:)|` +
`(?:${IPv6SegmentFormat}:){4}(?:(:${IPv6SegmentFormat}){0,1}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,3}|:)|` +
`(?:${IPv6SegmentFormat}:){3}(?:(:${IPv6SegmentFormat}){0,2}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,4}|:)|` +
`(?:${IPv6SegmentFormat}:){2}(?:(:${IPv6SegmentFormat}){0,3}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,5}|:)|` +
`(?:${IPv6SegmentFormat}:){1}(?:(:${IPv6SegmentFormat}){0,4}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,6}|:)|` +
`(?::((?::${IPv6SegmentFormat}){0,5}:${IPv4AddressFormat}|(?::${IPv6SegmentFormat}){1,7}|:))` +
')(%[0-9a-zA-Z-.:]{1,})?$',
);
return !IPv4AddressRegExp.test(value) && !IPv6AddressRegExp.test(value) && value !== '';
}
export function checkIpV6(value: string): boolean {
if (value === '' || typeof value === 'undefined' || value == null) {
return true;
}
const IPv4SegmentFormat = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])';
const IPv4AddressFormat = `(${IPv4SegmentFormat}[.]){3}${IPv4SegmentFormat}`;
const IPv6SegmentFormat = '(?:[0-9a-fA-F]{1,4})';
const IPv6AddressRegExp = new RegExp(
'^(' +
`(?:${IPv6SegmentFormat}:){7}(?:${IPv6SegmentFormat}|:)|` +
`(?:${IPv6SegmentFormat}:){6}(?:${IPv4AddressFormat}|:${IPv6SegmentFormat}|:)|` +
`(?:${IPv6SegmentFormat}:){5}(?::${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,2}|:)|` +
`(?:${IPv6SegmentFormat}:){4}(?:(:${IPv6SegmentFormat}){0,1}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,3}|:)|` +
`(?:${IPv6SegmentFormat}:){3}(?:(:${IPv6SegmentFormat}){0,2}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,4}|:)|` +
`(?:${IPv6SegmentFormat}:){2}(?:(:${IPv6SegmentFormat}){0,3}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,5}|:)|` +
`(?:${IPv6SegmentFormat}:){1}(?:(:${IPv6SegmentFormat}){0,4}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,6}|:)|` +
`(?::((?::${IPv6SegmentFormat}){0,5}:${IPv4AddressFormat}|(?::${IPv6SegmentFormat}){1,7}|:))` +
')(%[0-9a-zA-Z-.:]{1,})?$',
);
return !IPv6AddressRegExp.test(value) && value !== '';
}
export function checkCidr(value: string): boolean {
if (value === '') {
return true;
}
const reg =
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\/([0-9]|[1-2][0-9]|3[0-2]))?$/;
return !reg.test(value) && value !== '';
}
export function checkCidrV6(value: string): boolean {
if (value === '') {
return true;
}
if (checkIpV6(value.split('/')[0])) {
return true;
}
const reg = /^(?:[0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$/;
return !reg.test(value.split('/')[1]);
}
export function checkPort(value: string): boolean {
if (Number(value) <= 0) {
return true;
}
const reg = /^([1-9](\d{0,3}))$|^([1-5]\d{4})$|^(6[0-4]\d{3})$|^(65[0-4]\d{2})$|^(655[0-2]\d)$|^(6553[0-5])$/;
return !reg.test(value) && value !== '';
}
export function transferTimeToSecond(item: string): any {
if (item.indexOf('s') !== -1) {
return Number(item.replaceAll('s', ''));
}
if (item.indexOf('m') !== -1) {
return Number(item.replaceAll('m', '')) * 60;
}
if (item.indexOf('h') !== -1) {
return Number(item.replaceAll('h', '')) * 60 * 60;
}
if (item.indexOf('d') !== -1) {
return Number(item.replaceAll('d', '')) * 60 * 60 * 24;
}
return Number(item);
}
export function splitTime(item: string): any {
if (item.indexOf('s') !== -1) {
return { time: Number(item.replaceAll('s', '')), unit: 's' };
}
if (item.indexOf('m') !== -1) {
return { time: Number(item.replaceAll('m', '')), unit: 'm' };
}
if (item.indexOf('h') !== -1) {
return { time: Number(item.replaceAll('h', '')), unit: 'h' };
}
if (item.indexOf('d') !== -1) {
return { time: Number(item.replaceAll('d', '')), unit: 'd' };
}
if (item.indexOf('y') !== -1) {
return { time: Number(item.replaceAll('y', '')), unit: 'y' };
}
return { time: Number(item), unit: 's' };
}
export function transTimeUnit(val: string): any {
if (val.indexOf('s') !== -1) {
return val.replaceAll('s', i18n.global.t('commons.units.second'));
}
if (val.indexOf('m') !== -1) {
return val.replaceAll('m', i18n.global.t('commons.units.minute'));
}
if (val.indexOf('h') !== -1) {
return val.replaceAll('h', i18n.global.t('commons.units.hour'));
}
if (val.indexOf('d') !== -1) {
return val.replaceAll('d', i18n.global.t('commons.units.day'));
}
if (val.indexOf('y') !== -1) {
return val.replaceAll('y', i18n.global.t('commons.units.year'));
}
return val + i18n.global.t('commons.units.second');
}
export function splitTimeFromSecond(item: number): any {
if (item < 60) return { timeItem: item, timeUnit: 's' };
if (item < 3600) return { timeItem: item / 60, timeUnit: 'm' };
return { timeItem: item / 3600, timeUnit: 'h' };
}
export function splitHttp(url: string) {
if (url.indexOf('https://') != -1) {
return { proto: 'https', url: url.replaceAll('https://', '') };
}
if (url.indexOf('http://') != -1) {
return { proto: 'http', url: url.replaceAll('http://', '') };
}
return { proto: '', url: url };
}
export function spliceHttp(proto: string, url: string) {
return proto + '://' + url.replaceAll('https://', '').replaceAll('http://', '');
}
export function emptyLineFilter(str: string, spilt: string) {
let list = str.split(spilt);
let results = [];
for (let i = 0; i < list.length; i++) {
if (list[i].trim() !== '') {
results.push(list[i]);
}
}
return results.join(spilt);
}
@@ -109,7 +109,7 @@ import { createAgent, getAgentProviders, pageAgentAccounts } from '@/api/modules
import { AI } from '@/api/interface/ai';
import { getAppByKey, getAppDetail } from '@/api/modules/app';
import { getAgentSettingByKey } from '@/api/modules/setting';
import { getRandomStr, newUUID } from '@/utils/util';
import { getRandomStr, newUUID } from '@/utils/id';
import {
buildDefaultAllowedOrigin,
getAgentProviderDisplayName,
@@ -1,8 +1,7 @@
import { ref } from 'vue';
import { AI } from '@/api/interface/ai';
import { checkAgentPlugin, installAgentPlugin, uninstallAgentPlugin, upgradeAgentPlugin } from '@/api/modules/ai';
import { newUUID } from '@/utils/util';
import { newUUID } from '@/utils/id';
export const useAgentPluginChannel = (pluginType: AI.AgentPluginInstallReq['type']) => {
const agentId = ref(0);
const loading = ref(false);
@@ -30,7 +30,7 @@
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { loginAgentWeixinChannel } from '@/api/modules/ai';
import { newUUID } from '@/utils/util';
import { newUUID } from '@/utils/id';
import TaskLog from '@/components/log/task/index.vue';
import { isOpenclawCurrentHTTPVersion } from '@/utils/agent';
import PluginInstall from './components/plugin-install.vue';
@@ -147,7 +147,7 @@ import { AI } from '@/api/interface/ai';
import { installAgentSkill, listAgentSkills, searchAgentSkills, updateAgentSkill } from '@/api/modules/ai';
import { useGlobalStore } from '@/composables/useGlobalStore';
import { MsgSuccess } from '@/utils/message';
import { newUUID } from '@/utils/util';
import { newUUID } from '@/utils/id';
import { isOpenclawCurrentHTTPVersion } from '@/utils/agent';
import TaskLog from '@/components/log/task/index.vue';
import VersionSupport from './components/version-support.vue';
@@ -29,7 +29,7 @@
import { FormInstance } from 'element-plus';
import { ref } from 'vue';
import { deleteAgent } from '@/api/modules/ai';
import { newUUID } from '@/utils/util';
import { newUUID } from '@/utils/id';
import TaskLog from '@/components/log/task/index.vue';
const open = ref(false);
+2 -1
View File
@@ -210,7 +210,8 @@ import { AI } from '@/api/interface/ai';
import { App } from '@/api/interface/app';
import { Website } from '@/api/interface/website';
import { SearchWithPage } from '@/api/interface';
import { dateFormat, newUUID } from '@/utils/util';
import { dateFormat } from '@/utils/date';
import { newUUID } from '@/utils/id';
import { MsgSuccess } from '@/utils/message';
import AddDialog from '@/views/ai/agents/agent/add/index.vue';
+1 -1
View File
@@ -57,7 +57,7 @@ import AddDialog from '@/views/ai/agents/model/add/index.vue';
import ModelPoolDialog from '@/views/ai/agents/model/pool/index.vue';
import { ElMessageBox } from 'element-plus';
import i18n from '@/lang';
import { dateFormat } from '@/utils/util';
import { dateFormat } from '@/utils/date';
import { getAgentProviderDisplayName } from '@/utils/agent';
const items = ref<AI.AgentAccountItem[]>([]);
+1 -1
View File
@@ -168,7 +168,7 @@
<script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue';
import { loadGPUMonitor, getGPUOptions } from '@/api/modules/host';
import { dateFormatWithoutYear } from '@/utils/util';
import { dateFormatWithoutYear } from '@/utils/date';
import RouterMenu from '@/views/ai/gpu/index.vue';
import { GlobalStore } from '@/store';
import { shortcuts } from '@/utils/shortcuts';
@@ -93,7 +93,8 @@ import { listSSL, searchAcmeAccount } from '@/api/modules/website';
import { Rules } from '@/global/form-rules';
import { FormInstance, FormRules } from 'element-plus';
import { reactive, ref } from 'vue';
import { getAccountName, dateFormatSimple } from '@/utils/util';
import { getAccountName } from '@/utils/ssl';
import { dateFormatSimple } from '@/utils/date';
import { bindMcpDomain, getMcpDomain, updateMcpDomain } from '@/api/modules/ai';
import { MsgSuccess } from '@/utils/message';
import i18n from '@/lang';
+1 -1
View File
@@ -101,7 +101,7 @@ import { AI } from '@/api/interface/ai';
import { deleteMcpServer, operateMcpServer, pageMcpServer } from '@/api/modules/ai';
import RouterMenu from '@/views/ai/mcp/index.vue';
import { computed, onMounted, reactive, ref } from 'vue';
import { dateFormat } from '@/utils/util';
import { dateFormat } from '@/utils/date';
import McpServerOperate from './operate/index.vue';
import ComposeLogs from '@/components/log/compose/index.vue';
import { GlobalStore } from '@/store';
@@ -42,8 +42,7 @@ import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { MsgSuccess } from '@/utils/message';
import { createOllamaModel } from '@/api/modules/ai';
import { newUUID } from '@/utils/util';
import { newUUID } from '@/utils/id';
const drawerVisible = ref(false);
const form = reactive({
name: '',
@@ -98,7 +98,8 @@ import { listSSL, searchAcmeAccount } from '@/api/modules/website';
import { Rules } from '@/global/form-rules';
import { FormInstance, FormRules } from 'element-plus';
import { reactive, ref } from 'vue';
import { getAccountName, dateFormatSimple } from '@/utils/util';
import { getAccountName } from '@/utils/ssl';
import { dateFormatSimple } from '@/utils/date';
import { bindDomain, getBindDomain, updateBindDomain } from '@/api/modules/ai';
import { MsgSuccess } from '@/utils/message';
import i18n from '@/lang';
+2 -1
View File
@@ -193,7 +193,8 @@ import {
} from '@/api/modules/ai';
import { AI } from '@/api/interface/ai';
import { getAppPort } from '@/api/modules/app';
import { dateFormat, newUUID } from '@/utils/util';
import { dateFormat } from '@/utils/date';
import { newUUID } from '@/utils/id';
import { MsgInfo, MsgSuccess } from '@/utils/message';
import BindDomain from '@/views/ai/model/ollama/domain/index.vue';
import { routerToNameWithQuery } from '@/utils/router';
@@ -83,7 +83,7 @@ import PortJumpDialog from '@/components/port-jump/index.vue';
import PortJump from '@/views/website/runtime/components/port-jump.vue';
import { reactive, onMounted, ref } from 'vue';
import { dateFormat } from '@/utils/util';
import { dateFormat } from '@/utils/date';
import { AI } from '@/api/interface/ai';
import { deleteTensorRTLLM, operateTensorRTLLM, pageTensorRTLLM } from '@/api/modules/ai';
import { ElMessageBox } from 'element-plus';
+3 -1
View File
@@ -74,7 +74,7 @@ import { searchApp, syncApp, syncCutomAppStore, syncLocalApp, getCurrentNodeCust
import Install from '../detail/install/index.vue';
import router from '@/routers';
import { MsgSuccess } from '@/utils/message';
import { newUUID } from '@/utils/util';
import { newUUID } from '@/utils/id';
import Detail from '../detail/index.vue';
import TaskLog from '@/components/log/task/index.vue';
import bus from '@/global/bus';
@@ -185,7 +185,9 @@ const sync = async () => {
} else {
res = await syncApp(syncReq);
}
console.log(res);
if (res.message != '' && res.message != 'success') {
console.log(res.message);
MsgSuccess(res.message);
} else {
openTaskLog(taskID);
@@ -154,7 +154,7 @@ import { ref, watch } from 'vue';
import Params from '../params/index.vue';
import { Container } from '@/api/interface/container';
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
import { computeSizeFromMB } from '@/utils/util';
import { computeSizeFromMB } from '@/utils/size';
import { loadResourceLimit } from '@/api/modules/container';
import { useGlobalStore } from '@/composables/useGlobalStore';
import { loadOptionalComponent } from '@/extensions/optional';
@@ -78,7 +78,7 @@ import MarkDownEditor from '@/components/mkdown-editor/index.vue';
import { getAppByKey, getAppDetail, getAppIconUrl } from '@/api/modules/app';
import { ref } from 'vue';
import Install from './install/index.vue';
import { computeSizeFromMB } from '@/utils/util';
import { computeSizeFromMB } from '@/utils/size';
import { jumpToInstall } from '@/utils/app';
import { useGlobalStore } from '@/composables/useGlobalStore';
const { currentNode } = useGlobalStore();
@@ -25,7 +25,7 @@ import { useRouter } from 'vue-router';
import AppInstallForm from '@/views/app-store/detail/form/index.vue';
import { installApp } from '@/api/modules/app';
import { MsgError } from '@/utils/message';
import { newUUID } from '@/utils/util';
import { newUUID } from '@/utils/id';
import { routerToName } from '@/utils/router';
import TaskLog from '@/components/log/task/index.vue';
import i18n from '@/lang';
@@ -133,11 +133,11 @@
</template>
<script lang="ts" setup>
import { computed, onMounted, reactive, ref } from 'vue';
import { getRandomStr } from '@/utils/util';
import { getRandomStr } from '@/utils/id';
import { getAppService } from '@/api/modules/app';
import { Rules } from '@/global/form-rules';
import { App } from '@/api/interface/app';
import { getDBName, getLabel, getDescription } from '@/utils/util';
import { getDBName, getLabel, getDescription } from '@/utils/app-store';
import { getPathByType } from '@/api/modules/files';
import { loadFormatCollations } from '@/api/modules/database';
@@ -73,8 +73,8 @@
<script lang="ts" setup>
import { App } from '@/api/interface/app';
import { getAge, toLink } from '@/utils/util';
import { getAge } from '@/utils/date';
import { toLink } from '@/utils/browser';
interface Props {
installed: App.AppInstalled;
defaultLink: string;
@@ -187,7 +187,8 @@ import { reactive, ref } from 'vue';
import { FormInstance } from 'element-plus';
import { Rules, checkNumberRange } from '@/global/form-rules';
import { MsgError, MsgSuccess } from '@/utils/message';
import { getLabel, splitHttp, checkIpV4V6, checkDomain } from '@/utils/util';
import { getLabel } from '@/utils/app-store';
import { splitHttp, checkIpV4V6, checkDomain } from '@/utils/validate';
import i18n from '@/lang';
import { loadResourceLimit } from '@/api/modules/container';
import { Container } from '@/api/interface/container';
@@ -154,7 +154,7 @@ import Sortable from 'sortablejs';
import i18n from '@/lang';
import { ElMessageBox } from 'element-plus';
import { App } from '@/api/interface/app';
import { jumpToPath } from '@/utils/util';
import { jumpToPath } from '@/utils/router';
import { useRouter } from 'vue-router';
import { MsgSuccess } from '@/utils/message';
import { getAgentSettingByKey } from '@/api/modules/setting';
@@ -491,7 +491,8 @@ import i18n from '@/lang';
import { Container } from '@/api/interface/container';
import { routerToFileWithPath } from '@/utils/router';
import { MsgError, MsgSuccess } from '@/utils/message';
import { computeCPU, computeSize2, computeSizeForDocker, newUUID } from '@/utils/util';
import { computeCPU, computeSize2, computeSizeForDocker } from '@/utils/size';
import { newUUID } from '@/utils/id';
import { Rules } from '@/global/form-rules';
import { loadBaseDir } from '@/api/modules/setting';
import { ElCheckbox, ElForm } from 'element-plus';
@@ -53,8 +53,7 @@ import i18n from '@/lang';
import { commitContainer } from '@/api/modules/container';
import TaskLog from '@/components/log/task/index.vue';
import { MsgSuccess } from '@/utils/message';
import { newUUID } from '@/utils/util';
import { newUUID } from '@/utils/id';
const drawerVisible = ref<boolean>(false);
const emit = defineEmits<{ (e: 'search'): void }>();
const loading = ref(false);
@@ -124,8 +124,7 @@ import { MsgError, MsgSuccess, MsgWarning } from '@/utils/message';
import i18n from '@/lang';
import { ElInput, ElMessageBox, UploadFile, UploadInstance } from 'element-plus';
import { Document, FolderOpened, HomeFilled } from '@element-plus/icons-vue';
import { computeSize2 } from '@/utils/util';
import { computeSize2 } from '@/utils/size';
const visible = ref(false);
const title = ref('');
const containerID = ref('');
@@ -418,7 +418,8 @@ import { MsgSuccess, MsgWarning } from '@/utils/message';
import { GlobalStore } from '@/store';
import { routerToName, routerToNameWithQuery } from '@/utils/router';
import router from '@/routers';
import { computeSize2, computeSizeForDocker, computeCPU, newUUID } from '@/utils/util';
import { computeSize2, computeSizeForDocker, computeCPU } from '@/utils/size';
import { newUUID } from '@/utils/id';
import { updateCommonDescription } from '@/api/modules/setting';
const globalStore = GlobalStore();
@@ -170,7 +170,7 @@
import { ref } from 'vue';
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
import { routerToFileWithPath } from '@/utils/router';
import { copyText } from '@/utils/util';
import { copyText } from '@/utils/clipboard';
import i18n from '@/lang';
const visible = ref(false);
@@ -59,7 +59,7 @@
<script lang="ts" setup>
import { onBeforeUnmount, ref } from 'vue';
import { containerStats } from '@/api/modules/container';
import { dateFormatForSecond } from '@/utils/util';
import { dateFormatForSecond } from '@/utils/date';
import VCharts from '@/components/v-charts/index.vue';
import i18n from '@/lang';
@@ -357,7 +357,8 @@ import {
import { Container } from '@/api/interface/container';
import { MsgError } from '@/utils/message';
import TaskLog from '@/components/log/task/index.vue';
import { checkIpV4V6, checkPort, newUUID } from '@/utils/util';
import { checkIpV4V6, checkPort } from '@/utils/validate';
import { newUUID } from '@/utils/id';
import router from '@/routers';
import TerminalDialog from '@/views/host/file-management/terminal/index.vue';
import { routerToName, routerToNameWithQuery } from '@/utils/router';
@@ -25,8 +25,7 @@
import { containerPrune } from '@/api/modules/container';
import TaskLog from '@/components/log/task/index.vue';
import { ref } from 'vue';
import { newUUID } from '@/utils/util';
import { newUUID } from '@/utils/id';
const loading = ref(false);
const open = ref<boolean>(false);
const taskLogRef = ref();
@@ -69,7 +69,7 @@ import { Rules } from '@/global/form-rules';
import TaskLog from '@/components/log/task/index.vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { newUUID } from '@/utils/util';
import { newUUID } from '@/utils/id';
import { CheckboxValueType, ElForm } from 'element-plus';
import { reactive, ref } from 'vue';
@@ -197,7 +197,8 @@
import { containerItemStats, containerPrune, loadContainerStatus, loadDaemonJson } from '@/api/modules/container';
import DockerStatus from '@/views/container/docker-status/index.vue';
import { getSettingInfo } from '@/api/modules/setting';
import { computeSize2, newUUID } from '@/utils/util';
import { computeSize2 } from '@/utils/size';
import { newUUID } from '@/utils/id';
import TaskLog from '@/components/log/task/index.vue';
import { routerToName } from '@/utils/router';
import { onMounted, reactive, ref } from 'vue';
@@ -60,7 +60,7 @@ import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { imageBuild } from '@/api/modules/container';
import TaskLog from '@/components/log/task/index.vue';
import { newUUID } from '@/utils/util';
import { newUUID } from '@/utils/id';
import { MsgSuccess } from '@/utils/message';
const drawerVisible = ref(false);
+3 -1
View File
@@ -157,7 +157,9 @@
<script lang="ts" setup>
import { reactive, ref, computed } from 'vue';
import { dateFormat, newUUID, computeSize2 } from '@/utils/util';
import { dateFormat } from '@/utils/date';
import { newUUID } from '@/utils/id';
import { computeSize2 } from '@/utils/size';
import { Container } from '@/api/interface/container';
import Pull from '@/views/container/image/pull/index.vue';
import Tag from '@/views/container/image/tag/index.vue';

Some files were not shown because too many files have changed in this diff Show More