mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-20 08:03:55 +08:00
feat: Enhance responsive design and layout adjustments for mobile devices (#13739)
This commit is contained in:
@@ -547,7 +547,6 @@ func (f FileOp) DownloadFileWithProcess(rawURL, dst, key string, options Downloa
|
||||
partInfo, runErr = out.Stat()
|
||||
}
|
||||
if runErr == nil {
|
||||
// Remember umask-derived permissions, but keep incomplete content private.
|
||||
runErr = out.Chmod(0600)
|
||||
}
|
||||
if runErr == nil {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>loading...</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -59,11 +59,14 @@ defineProps({
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
|
||||
.header-span {
|
||||
position: relative;
|
||||
@@ -72,6 +75,8 @@ defineProps({
|
||||
margin-left: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
@@ -90,6 +95,7 @@ defineProps({
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,4 +103,26 @@ defineProps({
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.home-card .header {
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
|
||||
.header-left {
|
||||
flex: 1 1 200px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
flex: 0 1 auto;
|
||||
max-width: 100%;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.header-right:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<span v-if="actions.more.length" class="fu-table-operations__action">
|
||||
<el-dropdown class="fu-table-more-button" :trigger="trigger" @command="handleButtonClick">
|
||||
<span class="fu-table-operations__dropdown-trigger">
|
||||
<el-button class="fu-table-operations__button" link type="primary" @click.stop>
|
||||
<el-button class="fu-table-operations__button" link type="primary">
|
||||
{{ t('fu.table.more') }}
|
||||
</el-button>
|
||||
</span>
|
||||
@@ -50,13 +50,19 @@ import { isOperationDisabled, isOperationVisible, type FuTableOperationButton }
|
||||
|
||||
defineOptions({ name: 'FuTableOperationActions' });
|
||||
|
||||
type DropdownTrigger = 'hover' | 'click' | 'contextmenu';
|
||||
type DropdownTriggerValue = DropdownTrigger | DropdownTrigger[];
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
buttons: { type: Array as PropType<FuTableOperationButton[]>, default: () => [] },
|
||||
row: { type: Object, required: true },
|
||||
ellipsis: { type: Number, default: 2 },
|
||||
extra: { type: Number, default: 0 },
|
||||
trigger: { type: String, default: 'hover' },
|
||||
trigger: {
|
||||
type: [String, Array] as PropType<DropdownTriggerValue>,
|
||||
default: 'hover',
|
||||
},
|
||||
dropdownStyle: { type: Object as PropType<Record<string, string> | undefined>, default: undefined },
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:row="cardRow"
|
||||
:ellipsis="ellipsis"
|
||||
:extra="2"
|
||||
:trigger="trigger"
|
||||
:trigger="resolvedTrigger"
|
||||
:dropdown-style="dropdownStyle"
|
||||
/>
|
||||
<el-table-column
|
||||
@@ -22,7 +22,7 @@
|
||||
:buttons="buttons"
|
||||
:row="row"
|
||||
:ellipsis="ellipsis"
|
||||
:trigger="trigger"
|
||||
:trigger="resolvedTrigger"
|
||||
:dropdown-style="dropdownStyle"
|
||||
/>
|
||||
</template>
|
||||
@@ -31,12 +31,16 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, type PropType } from 'vue';
|
||||
import { useMediaQuery } from '@vueuse/core';
|
||||
|
||||
import FuTableOperationActions from './TableOperationActions.vue';
|
||||
import type { FuTableOperationButton } from './shared';
|
||||
|
||||
defineOptions({ name: 'FuTableOperations' });
|
||||
|
||||
type DropdownTrigger = 'hover' | 'click' | 'contextmenu';
|
||||
type DropdownTriggerValue = DropdownTrigger | DropdownTrigger[];
|
||||
|
||||
const normalizeWidth = (value?: string | number) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return undefined;
|
||||
@@ -77,8 +81,8 @@ const props = defineProps({
|
||||
default: 2,
|
||||
},
|
||||
trigger: {
|
||||
type: String,
|
||||
default: 'hover',
|
||||
type: [String, Array] as PropType<DropdownTriggerValue>,
|
||||
default: undefined,
|
||||
},
|
||||
fixed: {
|
||||
type: [Boolean, String],
|
||||
@@ -98,6 +102,11 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const hasFinePointer = useMediaQuery('(hover: hover) and (pointer: fine)');
|
||||
const resolvedTrigger = computed<DropdownTriggerValue>(
|
||||
() => props.trigger ?? (hasFinePointer.value ? 'hover' : 'click'),
|
||||
);
|
||||
|
||||
const resolvedFixed = computed(() => {
|
||||
if (props.fixed !== undefined) {
|
||||
return props.fixed;
|
||||
|
||||
@@ -729,4 +729,43 @@ onBeforeUnmount(() => {
|
||||
min-width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.table-footer-container {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 12px;
|
||||
|
||||
.footer-left {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
flex-shrink: 1;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.footer-left-button) {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:deep(.footer-left-button .el-select) {
|
||||
flex: 1 1 auto;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.complex-table__pagination {
|
||||
flex: 0 1 auto;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -72,6 +72,9 @@ const aiNotice = ref({
|
||||
message: '',
|
||||
});
|
||||
let aiNoticeTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let resizeFrame: number | undefined;
|
||||
let lastResizeColumns = 0;
|
||||
let lastResizeRows = 0;
|
||||
|
||||
const readyWatcher = watch(
|
||||
() => webSocketReady.value && termReady.value,
|
||||
@@ -214,6 +217,12 @@ function onClose(isKeepShow: boolean = false) {
|
||||
closing = true;
|
||||
stopReconnect();
|
||||
window.removeEventListener('resize', changeTerminalSize);
|
||||
if (resizeFrame !== undefined) {
|
||||
cancelAnimationFrame(resizeFrame);
|
||||
resizeFrame = undefined;
|
||||
}
|
||||
lastResizeColumns = 0;
|
||||
lastResizeRows = 0;
|
||||
clearAINotice();
|
||||
webSocketReady.value = false;
|
||||
try {
|
||||
@@ -239,6 +248,8 @@ function onClose(isKeepShow: boolean = false) {
|
||||
|
||||
const initTerminal = (online: boolean = false): boolean => {
|
||||
newTerm();
|
||||
lastResizeColumns = 0;
|
||||
lastResizeRows = 0;
|
||||
if (terminalElement.value) {
|
||||
term.value.open(terminalElement.value);
|
||||
applyTerminalBackground(terminalStore.backgroundColor);
|
||||
@@ -253,6 +264,16 @@ const initTerminal = (online: boolean = false): boolean => {
|
||||
};
|
||||
|
||||
function changeTerminalSize() {
|
||||
if (resizeFrame !== undefined) {
|
||||
return;
|
||||
}
|
||||
resizeFrame = requestAnimationFrame(() => {
|
||||
resizeFrame = undefined;
|
||||
resizeTerminal();
|
||||
});
|
||||
}
|
||||
|
||||
function resizeTerminal() {
|
||||
if (!terminalElement.value || !term.value) return;
|
||||
if (terminalElement.value.clientWidth <= 0 || terminalElement.value.clientHeight <= 0) {
|
||||
return;
|
||||
@@ -261,6 +282,11 @@ function changeTerminalSize() {
|
||||
fitAddon.fit();
|
||||
if (isWsOpen()) {
|
||||
const { cols, rows } = term.value;
|
||||
if (cols === lastResizeColumns && rows === lastResizeRows) {
|
||||
return;
|
||||
}
|
||||
lastResizeColumns = cols;
|
||||
lastResizeRows = rows;
|
||||
terminalSocket.value!.send(
|
||||
JSON.stringify({
|
||||
type: 'resize',
|
||||
@@ -335,6 +361,7 @@ const showWebSocketAuthError = (message: string) => {
|
||||
|
||||
const runRealTerminal = () => {
|
||||
webSocketReady.value = true;
|
||||
changeTerminalSize();
|
||||
term.value?.focus();
|
||||
// a reattached shell already ran its init command
|
||||
if (initCmd.value !== '' && !sessionId.value) {
|
||||
@@ -443,7 +470,7 @@ const closeRealTerminal = (ev: CloseEvent) => {
|
||||
}
|
||||
terminalSocket.value = undefined;
|
||||
if (closing || !sessionId.value) {
|
||||
term.value?.write('The connection has been disconnected.');
|
||||
term.value?.write('The connection has been disconnected.');
|
||||
term.value?.write(ev.reason);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,10 @@ import echarts from '@/utils/echarts';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
const { themeConfig } = useGlobalStore();
|
||||
const isDarkTheme = ref(false);
|
||||
const PieChartRef = ref<HTMLElement>();
|
||||
let mediaQuery: MediaQueryList;
|
||||
let resizeObserver: ResizeObserver | undefined;
|
||||
let resizeFrame: number | undefined;
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
@@ -28,7 +31,20 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
function changeChartSize() {
|
||||
echarts.getInstanceByDom(document.getElementById(props.id) as HTMLElement)?.resize();
|
||||
if (resizeFrame !== undefined) {
|
||||
return;
|
||||
}
|
||||
resizeFrame = requestAnimationFrame(() => {
|
||||
resizeFrame = undefined;
|
||||
resizeChart();
|
||||
});
|
||||
}
|
||||
|
||||
function resizeChart() {
|
||||
if (!PieChartRef.value) {
|
||||
return;
|
||||
}
|
||||
echarts.getInstanceByDom(PieChartRef.value)?.resize();
|
||||
}
|
||||
function getThemeColors() {
|
||||
return {
|
||||
@@ -45,14 +61,17 @@ function getThemeColors() {
|
||||
}
|
||||
|
||||
function initChart() {
|
||||
if (!PieChartRef.value) {
|
||||
return;
|
||||
}
|
||||
if (themeConfig.value.theme === 'auto') {
|
||||
isDarkTheme.value = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
} else {
|
||||
isDarkTheme.value = themeConfig.value.theme === 'dark';
|
||||
}
|
||||
let myChart = echarts?.getInstanceByDom(document.getElementById(props.id) as HTMLElement);
|
||||
let myChart = echarts?.getInstanceByDom(PieChartRef.value);
|
||||
if (myChart === null || myChart === undefined) {
|
||||
myChart = echarts.init(document.getElementById(props.id) as HTMLElement);
|
||||
myChart = echarts.init(PieChartRef.value);
|
||||
}
|
||||
let percentText = String(props.option.data).split('.');
|
||||
const { primaryLight2, primaryLight1, pieBgColor, textColor, subtextColor, shadowColor, backgroundStyleColor } =
|
||||
@@ -180,13 +199,24 @@ onMounted(() => {
|
||||
mediaQuery.addEventListener('change', handleThemeChange);
|
||||
initChart();
|
||||
window.addEventListener('resize', changeChartSize);
|
||||
if (PieChartRef.value) {
|
||||
resizeObserver = new ResizeObserver(changeChartSize);
|
||||
resizeObserver.observe(PieChartRef.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
echarts.getInstanceByDom(document.getElementById(props.id) as HTMLElement).dispose();
|
||||
if (resizeFrame !== undefined) {
|
||||
cancelAnimationFrame(resizeFrame);
|
||||
resizeFrame = undefined;
|
||||
}
|
||||
if (PieChartRef.value) {
|
||||
echarts.getInstanceByDom(PieChartRef.value)?.dispose();
|
||||
}
|
||||
window.removeEventListener('resize', changeChartSize);
|
||||
mediaQuery.removeEventListener('change', handleThemeChange);
|
||||
mediaQuery?.removeEventListener('change', handleThemeChange);
|
||||
resizeObserver?.disconnect();
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="footer" :class="{ 'footer--mobile': isMobile }">
|
||||
<div class="flex w-full flex-col gap-4 md:justify-between md:flex-row">
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<div class="footer-content">
|
||||
<div class="footer-copyright">
|
||||
<a v-if="!isIntl && !isFxplay" href="https://fit2cloud.com/" target="_blank">
|
||||
Copyright © 2014-{{ year }} {{ $t('commons.fit2cloud') }}
|
||||
</a>
|
||||
@@ -9,10 +9,8 @@
|
||||
Copyright © {{ year }} {{ $t('commons.lingxia') }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="footer-actions">
|
||||
<FooterNavigation />
|
||||
<SystemUpgrade />
|
||||
</div>
|
||||
<FooterNavigation class="footer-navigation-panel" />
|
||||
<SystemUpgrade class="footer-upgrade" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -37,7 +35,7 @@ const year = new Date().getFullYear();
|
||||
background: var(--panel-footer-bg);
|
||||
border-top: 1px solid var(--panel-footer-border);
|
||||
box-sizing: border-box;
|
||||
padding: 10px 20px;
|
||||
padding: 10px 12px;
|
||||
a {
|
||||
font-size: 12px;
|
||||
color: #858585;
|
||||
@@ -53,20 +51,52 @@ const year = new Date().getFullYear();
|
||||
}
|
||||
|
||||
.footer--mobile {
|
||||
min-height: 108px;
|
||||
min-height: 76px;
|
||||
}
|
||||
|
||||
.footer-actions {
|
||||
.footer-content {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
row-gap: 8px;
|
||||
justify-content: center;
|
||||
gap: 8px 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.footer-actions {
|
||||
column-gap: 8px;
|
||||
justify-content: center;
|
||||
.footer-copyright {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.footer-navigation-panel {
|
||||
order: -1;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
.footer-upgrade {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.footer-content {
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.footer-copyright {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.footer-navigation-panel {
|
||||
order: 0;
|
||||
flex-basis: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, watch } from 'vue';
|
||||
import { ref, computed, onBeforeUnmount, onMounted, watch } from 'vue';
|
||||
import { RouteRecordRaw, useRoute, useRouter } from 'vue-router';
|
||||
import { loadingSvg } from '@/utils/svg';
|
||||
import Logo from './components/Logo.vue';
|
||||
@@ -78,16 +78,12 @@ function buildRegisteredMenuList(source: RouteRecordRaw[]): RouteRecordRaw[] {
|
||||
}
|
||||
|
||||
const screenWidth = ref(0);
|
||||
const listeningWindow = () => {
|
||||
window.onresize = () => {
|
||||
return (() => {
|
||||
screenWidth.value = document.body.clientWidth;
|
||||
if (!isCollapse.value && screenWidth.value < 1200) menuStore.setCollapse();
|
||||
if (isCollapse.value && screenWidth.value > 1200) menuStore.setCollapse();
|
||||
})();
|
||||
};
|
||||
const handleWindowResize = () => {
|
||||
screenWidth.value = document.body.clientWidth;
|
||||
if (!isCollapse.value && screenWidth.value < 1200) menuStore.setCollapse();
|
||||
if (isCollapse.value && screenWidth.value > 1200) menuStore.setCollapse();
|
||||
};
|
||||
listeningWindow();
|
||||
window.addEventListener('resize', handleWindowResize);
|
||||
const emit = defineEmits(['menuClick', 'openTask']);
|
||||
const handleMenuClick = (path) => {
|
||||
emit('menuClick', path);
|
||||
@@ -285,12 +281,20 @@ function adjustAndCleanMenu(menuItem, list) {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
screenWidth.value = document.body.clientWidth;
|
||||
if (!isCollapse.value && screenWidth.value < 1200) {
|
||||
menuStore.setCollapse();
|
||||
}
|
||||
if (!menuStore.menuList || menuStore.menuList.length === 0) {
|
||||
menuStore.setMenuList(buildAuthVisibleMenuList(menuList));
|
||||
}
|
||||
search();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleWindowResize);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => [currentNode.value, isAdmin.value, permissions.value.join('|')],
|
||||
() => {
|
||||
|
||||
@@ -3,8 +3,8 @@ import { useRoute } from 'vue-router';
|
||||
import { MenuStore } from '@/store';
|
||||
import { DeviceType } from '@/enums/app';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
/** 参考 Bootstrap 的响应式设计 WIDTH = 600 */
|
||||
const WIDTH = 600;
|
||||
/** 与 Tailwind CSS 的 md 断点保持一致 */
|
||||
const MOBILE_BREAKPOINT = 768;
|
||||
|
||||
/** 根据大小变化重新布局 */
|
||||
export default () => {
|
||||
@@ -13,16 +13,20 @@ export default () => {
|
||||
const menuStore = MenuStore();
|
||||
const _isMobile = () => {
|
||||
const rect = document.body.getBoundingClientRect();
|
||||
return rect.width - 1 < WIDTH;
|
||||
return rect.width < MOBILE_BREAKPOINT;
|
||||
};
|
||||
|
||||
const _syncLayout = () => {
|
||||
const isMobileScreen = _isMobile();
|
||||
globalStore.toggleDevice(isMobileScreen ? DeviceType.Mobile : DeviceType.Desktop);
|
||||
if (isMobileScreen) {
|
||||
menuStore.closeSidebar(true);
|
||||
}
|
||||
};
|
||||
|
||||
const _resizeHandler = () => {
|
||||
if (!document.hidden) {
|
||||
const isMobileScreen = _isMobile();
|
||||
globalStore.toggleDevice(isMobileScreen ? DeviceType.Mobile : DeviceType.Desktop);
|
||||
if (isMobileScreen) {
|
||||
menuStore.closeSidebar(true);
|
||||
}
|
||||
_syncLayout();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -40,10 +44,7 @@ export default () => {
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (_isMobile()) {
|
||||
globalStore.toggleDevice(DeviceType.Mobile);
|
||||
menuStore.closeSidebar(true);
|
||||
}
|
||||
_syncLayout();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
|
||||
@@ -233,6 +233,8 @@ onMounted(() => {
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
min-width: 0;
|
||||
transition: margin-left 0.3s;
|
||||
margin-left: var(--panel-menu-width);
|
||||
background-color: var(--panel-main-bg-color-9);
|
||||
@@ -241,6 +243,8 @@ onMounted(() => {
|
||||
.app-main {
|
||||
padding: 7px 20px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
.app-sidebar {
|
||||
@@ -309,4 +313,11 @@ onMounted(() => {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.app-main {
|
||||
padding-right: 12px;
|
||||
padding-left: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -107,7 +107,7 @@ const TerminalSessionStore = defineStore('TerminalSessionStore', () => {
|
||||
if (r.status !== 'fulfilled') return;
|
||||
const fromLocalNode = i === 1 || node === 'local';
|
||||
for (const s of r.value.data || []) {
|
||||
if (s.kind !== 'local' && s.kind !== 'ssh') continue;
|
||||
if (s.kind !== 'local' && s.kind !== 'ssh') continue;
|
||||
// attached elsewhere = another browser tab is using it; do not steal it
|
||||
if (s.attached || entries.value.some((e) => e.sessionId === s.id)) continue;
|
||||
if (s.hostId > 0 && !fromLocalNode) continue; // ssh sessions are served by the local node
|
||||
|
||||
@@ -78,7 +78,8 @@ html {
|
||||
|
||||
.input-help {
|
||||
font-size: 12px;
|
||||
word-break: keep-all;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
color: #adb0bc;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
@@ -112,8 +113,10 @@ html {
|
||||
left: 45%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 400px;
|
||||
max-width: calc(100% - 24px);
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
overflow-wrap: anywhere;
|
||||
.bt {
|
||||
margin-top: -2px;
|
||||
}
|
||||
@@ -313,6 +316,22 @@ html {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.mask-prompt {
|
||||
left: 50%;
|
||||
width: calc(100% - 24px);
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
.el-button + .el-button {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.monaco-editor-tree-light .el-tree-node__content:hover {
|
||||
background-color: #e5eefd;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
.mobile {
|
||||
.monitor-tags {
|
||||
position: inherit;
|
||||
top: 13px;
|
||||
}
|
||||
.mobile-monitor-chart {
|
||||
margin-top: 20px !important;
|
||||
}
|
||||
.search-button {
|
||||
float: none !important;
|
||||
.table-button {
|
||||
@@ -36,23 +29,28 @@
|
||||
.router_card_button {
|
||||
padding: 2px 0;
|
||||
}
|
||||
.el-drawer.rtl {
|
||||
width: 80% !important;
|
||||
}
|
||||
.site-form-wrapper {
|
||||
box-sizing: border-box;
|
||||
width: 90% !important;
|
||||
max-width: 100%;
|
||||
min-width: auto !important;
|
||||
.el-form-item__label {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
.moblie-form {
|
||||
overflow: auto;
|
||||
.el-input {
|
||||
width: 350px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
|
||||
.el-input,
|
||||
.el-textarea {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.el-textarea__inner {
|
||||
width: 350px;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,12 +76,76 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
@media only screen and (max-width: 767px) {
|
||||
.el-col-xs-24 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
.el-dialog {
|
||||
--el-dialog-width: 80% !important;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1024px) {
|
||||
.el-dialog:not(.is-fullscreen) {
|
||||
min-width: min(420px, calc(100vw - 24px));
|
||||
max-width: calc(100vw - 24px);
|
||||
max-height: calc(100vh - 24px);
|
||||
max-height: calc(100dvh - 24px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
.el-dialog__header,
|
||||
.el-dialog__footer {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
}
|
||||
|
||||
.el-drawer {
|
||||
max-width: 100vw;
|
||||
max-height: 100vh;
|
||||
max-height: 100dvh;
|
||||
|
||||
&.rtl,
|
||||
&.ltr {
|
||||
min-width: min(100vw, 480px);
|
||||
max-width: 100vw;
|
||||
}
|
||||
|
||||
.el-drawer__header,
|
||||
.el-drawer__footer {
|
||||
flex: 0 0 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.el-drawer__body {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.el-dialog:not(.is-fullscreen) {
|
||||
width: calc(100vw - 24px) !important;
|
||||
min-width: 0;
|
||||
max-width: calc(100vw - 24px);
|
||||
margin: 12px auto;
|
||||
}
|
||||
|
||||
.el-picker__popper {
|
||||
max-width: calc(100vw - 24px);
|
||||
|
||||
.el-picker-panel {
|
||||
max-width: calc(100vw - 24px);
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,12 +111,12 @@
|
||||
<el-tab-pane :label="$t('commons.table.port')">
|
||||
<div class="mt-1.5">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-col :span="8" :xs="24">
|
||||
<el-form-item :label="$t('commons.table.port')" prop="port">
|
||||
<el-input v-model.number="mcpServer.port" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="6" :xs="24">
|
||||
<el-form-item :label="$t('app.allowPort')" prop="hostIP">
|
||||
<el-switch
|
||||
v-model="mcpServer.hostIP"
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="install-card">
|
||||
<el-card class="e-card">
|
||||
<el-row :gutter="10">
|
||||
<el-col :xs="3" :sm="3" :md="3" :lg="4" :xl="3">
|
||||
<el-row :gutter="10" class="install-card-row">
|
||||
<el-col class="install-card-icon-col" :xs="3" :sm="3" :md="3" :lg="4" :xl="3">
|
||||
<AppIcon
|
||||
@open-detail="$emit('openDetail')"
|
||||
:appKey="installed.appKey"
|
||||
:currentNode="currentNode"
|
||||
></AppIcon>
|
||||
</el-col>
|
||||
<el-col :xs="21" :sm="21" :md="21" :lg="20" :xl="21">
|
||||
<el-col class="install-card-detail-col" :xs="21" :sm="21" :md="21" :lg="20" :xl="21">
|
||||
<div class="a-detail">
|
||||
<AppHeader
|
||||
:installed="installed"
|
||||
@@ -73,4 +73,90 @@ defineEmits([
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use '@/views/app-store/index.scss';
|
||||
|
||||
@media only screen and (max-width: 1023px) {
|
||||
.install-card-row {
|
||||
display: grid;
|
||||
grid-template-columns: 87px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.install-card-icon-col,
|
||||
.install-card-detail-col {
|
||||
width: auto;
|
||||
max-width: none;
|
||||
min-width: 0;
|
||||
flex: initial;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.install-card-row {
|
||||
grid-template-columns: 74px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.install-card-icon-col {
|
||||
:deep(.el-avatar) {
|
||||
max-width: 64px;
|
||||
max-height: 64px;
|
||||
}
|
||||
}
|
||||
|
||||
.install-card-row .install-card-detail-col .a-detail {
|
||||
min-width: 0;
|
||||
|
||||
:deep(.d-name .d-name-row) {
|
||||
min-width: 0;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
:deep(.d-name .d-name-row .name-actions) {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
flex: 0 1 auto;
|
||||
flex-wrap: wrap;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
:deep(.d-name .d-name-row .name-wrap) {
|
||||
min-width: min(100px, 100%);
|
||||
max-width: calc(100% - 56px);
|
||||
flex: 1 1 100px;
|
||||
}
|
||||
|
||||
:deep(.d-name .d-name-row .operate-actions) {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
flex-shrink: 1;
|
||||
justify-content: flex-start;
|
||||
|
||||
.el-button + .el-button {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.h-button {
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.d-description),
|
||||
:deep(.d-button) {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:deep(.d-description .el-button) {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
min-height: 24px;
|
||||
white-space: normal;
|
||||
|
||||
> span {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -32,12 +32,17 @@
|
||||
<template #main>
|
||||
<div>
|
||||
<MainDiv :heightDiff="mode === 'upgrade' ? 280 : 300">
|
||||
<el-alert type="info" :closable="false" v-if="mode === 'installed' && !isIntl">
|
||||
<el-alert
|
||||
class="app-install-alert"
|
||||
type="info"
|
||||
:closable="false"
|
||||
v-if="mode === 'installed' && !isIntl"
|
||||
>
|
||||
<template #title>
|
||||
<span class="flx-align-center">
|
||||
{{ $t('app.installHelper') }}
|
||||
<span class="flx-align-center app-install-helper">
|
||||
<span class="app-install-helper-text">{{ $t('app.installHelper') }}</span>
|
||||
<el-link
|
||||
class="ml-5"
|
||||
class="app-install-helper-link"
|
||||
icon="Position"
|
||||
@click="jumpToPath(router, '/containers/setting')"
|
||||
type="primary"
|
||||
@@ -565,4 +570,40 @@ onUnmounted(() => {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.app-install-helper {
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 8px;
|
||||
}
|
||||
|
||||
.app-install-helper-text {
|
||||
min-width: 0;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.app-install-helper-link {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.app-install-alert {
|
||||
:deep(.el-alert__content),
|
||||
:deep(.el-alert__title) {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.app-install-helper {
|
||||
width: 100%;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.app-install-helper-text {
|
||||
flex: 0 0 100%;
|
||||
width: 100%;
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -39,15 +39,15 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</CardWithHeader>
|
||||
<el-row :gutter="7" class="card-interval">
|
||||
<el-col :span="8">
|
||||
<el-row :gutter="7" class="card-interval container-stat-row">
|
||||
<el-col :span="8" :xs="24">
|
||||
<CardWithHeader :header="$t('container.compose')">
|
||||
<template #body>
|
||||
<span class="count" @click="routerToName('Compose')">{{ countItem.composeCount }}</span>
|
||||
</template>
|
||||
</CardWithHeader>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="8" :xs="24">
|
||||
<CardWithHeader :header="$t('container.composeTemplate')">
|
||||
<template #body>
|
||||
<span class="count" @click="routerToName('ComposeTemplate')">
|
||||
@@ -56,7 +56,7 @@
|
||||
</template>
|
||||
</CardWithHeader>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="8" :xs="24">
|
||||
<CardWithHeader :header="$t('container.image')">
|
||||
<template #body>
|
||||
<span class="count" @click="routerToName('Image')">{{ countItem.imageCount }}</span>
|
||||
@@ -64,22 +64,22 @@
|
||||
</CardWithHeader>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="7" class="card-interval">
|
||||
<el-col :span="8">
|
||||
<el-row :gutter="7" class="card-interval container-stat-row">
|
||||
<el-col :span="8" :xs="24">
|
||||
<CardWithHeader :header="$t('container.imageRepo')">
|
||||
<template #body>
|
||||
<span class="count" @click="routerToName('Repo')">{{ countItem.repoCount }}</span>
|
||||
</template>
|
||||
</CardWithHeader>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="8" :xs="24">
|
||||
<CardWithHeader :header="$t('container.network')">
|
||||
<template #body>
|
||||
<span class="count" @click="routerToName('Network')">{{ countItem.networkCount }}</span>
|
||||
</template>
|
||||
</CardWithHeader>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="8" :xs="24">
|
||||
<CardWithHeader :header="$t('container.volume')">
|
||||
<template #body>
|
||||
<span class="count" @click="routerToName('Volume')">{{ countItem.volumeCount }}</span>
|
||||
@@ -94,10 +94,11 @@
|
||||
direction="vertical"
|
||||
align="center"
|
||||
v-loading="usageLoading"
|
||||
:column="4"
|
||||
:column="isMobile ? 2 : 4"
|
||||
:label-width="isMobile ? '50%' : '25%'"
|
||||
class="mt-2"
|
||||
>
|
||||
<el-descriptions-item label-width="25%" align="center" :label="$t('container.image')">
|
||||
<el-descriptions-item align="center" :label="$t('container.image')">
|
||||
{{
|
||||
$t('container.usage', [
|
||||
computeSize2(countItem.imageUsage),
|
||||
@@ -115,7 +116,7 @@
|
||||
{{ $t('container.clean') }}
|
||||
</el-button>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label-width="25%" align="center" :label="$t('menu.container')">
|
||||
<el-descriptions-item align="center" :label="$t('menu.container')">
|
||||
{{
|
||||
$t('container.usage', [
|
||||
computeSize2(countItem.containerUsage),
|
||||
@@ -133,7 +134,7 @@
|
||||
{{ $t('container.clean') }}
|
||||
</el-button>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label-width="25%" align="center" :label="$t('container.localVolume')">
|
||||
<el-descriptions-item align="center" :label="$t('container.localVolume')">
|
||||
{{
|
||||
$t('container.usage', [
|
||||
computeSize2(countItem.volumeUsage),
|
||||
@@ -151,7 +152,7 @@
|
||||
{{ $t('container.clean') }}
|
||||
</el-button>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label-width="25%" align="center" :label="$t('container.buildCache')">
|
||||
<el-descriptions-item align="center" :label="$t('container.buildCache')">
|
||||
{{
|
||||
$t('container.usage', [
|
||||
computeSize2(countItem.buildCacheUsage),
|
||||
@@ -207,8 +208,10 @@ import TaskLog from '@/components/log/task/index.vue';
|
||||
import { routerToName } from '@/utils/router';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const taskLogRef = ref();
|
||||
const { isMobile } = useGlobalStore();
|
||||
|
||||
const loading = ref();
|
||||
const usageLoading = ref(false);
|
||||
@@ -353,4 +356,10 @@ onMounted(() => {
|
||||
line-height: 32px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.container-stat-row {
|
||||
row-gap: 7px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -49,19 +49,22 @@
|
||||
label-width="auto"
|
||||
>
|
||||
<el-form-item :label="$t('container.mirrors')" prop="mirrors">
|
||||
<div class="w-full" v-if="form.mirrors">
|
||||
<div
|
||||
class="flex w-full min-w-0 flex-col items-start gap-2 md:flex-row md:gap-0"
|
||||
v-if="form.mirrors"
|
||||
>
|
||||
<el-input
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
disabled
|
||||
v-model="form.mirrors"
|
||||
style="width: calc(100% - 80px)"
|
||||
class="w-full min-w-0 md:flex-1"
|
||||
/>
|
||||
<el-button
|
||||
v-permission
|
||||
@click="onChangeMirrors"
|
||||
icon="Setting"
|
||||
class="custom-input-textarea"
|
||||
class="custom-input-textarea w-full md:w-auto"
|
||||
>
|
||||
{{ $t('commons.button.set') }}
|
||||
</el-button>
|
||||
@@ -74,7 +77,7 @@
|
||||
</template>
|
||||
</el-input>
|
||||
<span class="input-help">{{ $t('container.mirrorsHelper') }}</span>
|
||||
<span class="input-help flex flx-align-center" v-if="!isFxplay">
|
||||
<span class="input-help flex min-w-0 flex-wrap flx-align-center" v-if="!isFxplay">
|
||||
{{ $t('container.mirrorsHelper2') }}
|
||||
<el-link class="p-ml-5 text-xs" icon="Position" @click="toDoc()" type="primary">
|
||||
{{ $t('firewall.quickJump') }}
|
||||
@@ -82,15 +85,23 @@
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.registries')" prop="registries">
|
||||
<div class="w-full" v-if="form.registries">
|
||||
<div
|
||||
class="flex w-full min-w-0 flex-col items-start gap-2 md:flex-row md:gap-0"
|
||||
v-if="form.registries"
|
||||
>
|
||||
<el-input
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
disabled
|
||||
v-model="form.registries"
|
||||
style="width: calc(100% - 80px)"
|
||||
class="w-full min-w-0 md:flex-1"
|
||||
/>
|
||||
<el-button v-permission @click="onChangeRegistries" icon="Setting">
|
||||
<el-button
|
||||
v-permission
|
||||
class="w-full md:w-auto"
|
||||
@click="onChangeRegistries"
|
||||
icon="Setting"
|
||||
>
|
||||
{{ $t('commons.button.set') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<el-popover
|
||||
v-if="dialogData.rowData.name.length >= 15"
|
||||
placement="top-start"
|
||||
trigger="hover"
|
||||
:trigger="hasFinePointer ? 'hover' : 'click'"
|
||||
width="250"
|
||||
:content="$t('cronjob.' + dialogData.rowData.type) + ' - ' + dialogData.rowData.name"
|
||||
>
|
||||
@@ -80,7 +80,7 @@
|
||||
<LayoutContent :title="$t('cronjob.record')" :reload="true">
|
||||
<template #rightToolBar>
|
||||
<el-date-picker
|
||||
class="mr-2.5"
|
||||
class="mr-2.5 record-time-range"
|
||||
@change="search(true)"
|
||||
v-model="timeRangeLoad"
|
||||
type="datetimerange"
|
||||
@@ -257,6 +257,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useMediaQuery } from '@vueuse/core';
|
||||
import { Cronjob } from '@/api/interface/cronjob';
|
||||
import { searchRecords, handleOnce, updateStatus, cleanRecords, stopCronjob } from '@/api/modules/cronjob';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
@@ -269,6 +270,8 @@ import { listAppInstalled } from '@/api/modules/app';
|
||||
import { shortcuts } from '@/utils/shortcuts';
|
||||
import { hasBackup } from '../helper';
|
||||
|
||||
const hasFinePointer = useMediaQuery('(hover: hover) and (pointer: fine)');
|
||||
|
||||
const loading = ref();
|
||||
const hasRecords = ref();
|
||||
|
||||
@@ -480,6 +483,7 @@ defineExpose({
|
||||
<style lang="scss" scoped>
|
||||
.infinite-list {
|
||||
height: calc(100vh - 320px);
|
||||
height: calc(100dvh - 320px);
|
||||
.select-sign {
|
||||
&::before {
|
||||
float: left;
|
||||
@@ -518,4 +522,46 @@ defineExpose({
|
||||
min-width: 1200px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1024px) {
|
||||
.mainClass {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.mainRowClass {
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
|
||||
> .el-col {
|
||||
flex: 0 0 100%;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.infinite-list {
|
||||
height: 320px;
|
||||
height: clamp(220px, 38dvh, 360px);
|
||||
}
|
||||
|
||||
.descriptionWide,
|
||||
.description {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.page-item {
|
||||
float: none;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
:global(.record-time-range.el-date-editor) {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,12 +10,17 @@
|
||||
]"
|
||||
/>
|
||||
|
||||
<el-alert v-if="!isSafety && showEntranceWarn" class="card-interval" type="warning" @close="hideEntrance">
|
||||
<el-alert
|
||||
v-if="!isSafety && showEntranceWarn"
|
||||
class="card-interval dashboard-entrance-alert"
|
||||
type="warning"
|
||||
@close="hideEntrance"
|
||||
>
|
||||
<template #title>
|
||||
<span class="flx-align-center">
|
||||
<span class="flx-align-center dashboard-entrance-alert-title">
|
||||
<span>{{ $t('home.entranceHelper') }}</span>
|
||||
<el-link
|
||||
style="font-size: 12px; margin-left: 5px"
|
||||
class="dashboard-entrance-alert-link"
|
||||
icon="Position"
|
||||
v-if="isAdmin"
|
||||
@click="jumpToPath(router, '/settings/safe')"
|
||||
@@ -123,7 +128,7 @@
|
||||
</el-select>
|
||||
</template>
|
||||
<template #body>
|
||||
<div style="position: relative; margin-top: 60px">
|
||||
<div class="monitor-chart-content">
|
||||
<div class="monitor-tags" :style="monitorTagsStyle" v-if="chartOption === 'network'">
|
||||
<el-tag>
|
||||
{{ $t('monitor.up') }}: {{ computeSizeFromKBs(currentChartInfo.netBytesSent) }}
|
||||
@@ -144,7 +149,7 @@
|
||||
<el-tag>{{ $t('home.ioDelay') }}: {{ currentChartInfo.ioTime }} ms</el-tag>
|
||||
</div>
|
||||
|
||||
<div v-if="chartOption === 'io'" style="margin-top: 40px" class="mobile-monitor-chart">
|
||||
<div v-if="chartOption === 'io'" class="mobile-monitor-chart">
|
||||
<v-charts
|
||||
height="383px"
|
||||
id="ioChart"
|
||||
@@ -153,7 +158,7 @@
|
||||
:dataZoom="true"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="chartOption === 'network'" style="margin-top: 40px" class="mobile-monitor-chart">
|
||||
<div v-if="chartOption === 'network'" class="mobile-monitor-chart">
|
||||
<v-charts
|
||||
height="383px"
|
||||
id="networkChart"
|
||||
@@ -1160,6 +1165,55 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dashboard-entrance-alert-title {
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 8px;
|
||||
|
||||
> span {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-entrance-alert-link {
|
||||
flex: 0 0 auto;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.monitor-chart-content {
|
||||
position: relative;
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
.mobile-monitor-chart {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.dashboard-entrance-alert {
|
||||
padding-right: 40px;
|
||||
|
||||
:deep(.el-alert__content),
|
||||
:deep(.el-alert__title) {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-entrance-alert-title {
|
||||
width: 100%;
|
||||
align-items: flex-start;
|
||||
|
||||
> span {
|
||||
flex: 0 0 100%;
|
||||
width: 100%;
|
||||
overflow-wrap: break-word;
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 992px) {
|
||||
.dashboard-right {
|
||||
contain: size;
|
||||
@@ -1285,6 +1339,44 @@ onBeforeUnmount(() => {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1024px) {
|
||||
.monitor-chart-content {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.monitor-chart-content .monitor-tags {
|
||||
position: static;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
:deep(.el-tag) {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
min-height: 24px;
|
||||
justify-content: center;
|
||||
padding-block: 3px;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-monitor-chart {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) and (max-width: 1024px) {
|
||||
.monitor-chart-content .monitor-tags {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
.version {
|
||||
font-size: 14px;
|
||||
color: #858585;
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
:hide-after="20"
|
||||
:teleported="false"
|
||||
:width="320"
|
||||
popper-class="dashboard-status-popover"
|
||||
:trigger="hasFinePointer ? 'hover' : 'click'"
|
||||
v-if="chartsOption['load']"
|
||||
@hide="onCpuPopoverHide"
|
||||
>
|
||||
@@ -55,6 +57,8 @@
|
||||
:hide-after="20"
|
||||
:teleported="false"
|
||||
:width="430"
|
||||
popper-class="dashboard-status-popover"
|
||||
:trigger="hasFinePointer ? 'hover' : 'click'"
|
||||
v-if="chartsOption['cpu']"
|
||||
@hide="onCpuPopoverHide"
|
||||
>
|
||||
@@ -161,6 +165,8 @@
|
||||
:hide-after="20"
|
||||
:teleported="false"
|
||||
:width="480"
|
||||
popper-class="dashboard-status-popover"
|
||||
:trigger="hasFinePointer ? 'hover' : 'click'"
|
||||
v-if="chartsOption['memory']"
|
||||
@hide="onMemPopoverHide"
|
||||
>
|
||||
@@ -249,7 +255,13 @@
|
||||
</el-col>
|
||||
<template v-for="(item, index) of currentInfo.diskData" :key="index">
|
||||
<el-col :xs="6" :sm="6" :md="3" :lg="3" :xl="3" align="center" v-if="isShow('disk', index)">
|
||||
<el-popover :hide-after="20" :teleported="false" :width="450" v-if="chartsOption[`disk${index}`]">
|
||||
<el-popover
|
||||
:hide-after="20"
|
||||
:teleported="false"
|
||||
:width="450"
|
||||
popper-class="dashboard-status-popover"
|
||||
v-if="chartsOption[`disk${index}`]"
|
||||
>
|
||||
<el-descriptions :column="1" size="small">
|
||||
<el-descriptions-item :label="$t('home.mount')">
|
||||
{{ item.path }}
|
||||
@@ -300,7 +312,13 @@
|
||||
</template>
|
||||
<template v-for="(item, index) of currentInfo.gpuData" :key="index">
|
||||
<el-col :xs="6" :sm="6" :md="3" :lg="3" :xl="3" align="center" v-if="isShow('gpu', index)">
|
||||
<el-popover :hide-after="20" :teleported="false" :width="450" v-if="chartsOption[`gpu${index}`]">
|
||||
<el-popover
|
||||
:hide-after="20"
|
||||
:teleported="false"
|
||||
:width="450"
|
||||
popper-class="dashboard-status-popover"
|
||||
v-if="chartsOption[`gpu${index}`]"
|
||||
>
|
||||
<el-descriptions :title="item.productName" direction="vertical" :column="3" size="small">
|
||||
<el-descriptions-item :label="$t('aiTools.gpu.gpuUtil')">
|
||||
{{ item.gpuUtil }}
|
||||
@@ -346,7 +364,13 @@
|
||||
</template>
|
||||
<template v-for="(item, index) of currentInfo.npuData" :key="index">
|
||||
<el-col :xs="6" :sm="6" :md="3" :lg="3" :xl="3" align="center" v-if="isShow('npu', index)">
|
||||
<el-popover :hide-after="20" :teleported="false" :width="450" v-if="chartsOption[`npu${index}`]">
|
||||
<el-popover
|
||||
:hide-after="20"
|
||||
:teleported="false"
|
||||
:width="450"
|
||||
popper-class="dashboard-status-popover"
|
||||
v-if="chartsOption[`npu${index}`]"
|
||||
>
|
||||
<el-descriptions :title="item.productName" direction="vertical" :column="3" size="small">
|
||||
<el-descriptions-item v-if="hasField(item.aiCore)" label="AICore(%)">
|
||||
{{ item.aiCore }}
|
||||
@@ -395,7 +419,13 @@
|
||||
</template>
|
||||
<template v-for="(item, index) of currentInfo.xpuData" :key="index">
|
||||
<el-col :xs="6" :sm="6" :md="3" :lg="3" :xl="3" align="center" v-if="isShow('xpu', index)">
|
||||
<el-popover :hide-after="20" :teleported="false" :width="400" v-if="chartsOption[`xpu${index}`]">
|
||||
<el-popover
|
||||
:hide-after="20"
|
||||
:teleported="false"
|
||||
:width="400"
|
||||
popper-class="dashboard-status-popover"
|
||||
v-if="chartsOption[`xpu${index}`]"
|
||||
>
|
||||
<el-descriptions :title="item.deviceName" direction="vertical" :column="3" size="small">
|
||||
<el-descriptions-item v-if="hasField(item.gpuUtil)" :label="$t('aiTools.gpu.gpuUtil')">
|
||||
{{ item.gpuUtil }}
|
||||
@@ -449,10 +479,13 @@ import { Dashboard } from '@/api/interface/dashboard';
|
||||
import { computeSize } from '@/utils/size';
|
||||
import i18n from '@/lang';
|
||||
import { nextTick, onBeforeUnmount, ref } from 'vue';
|
||||
import { useMediaQuery } from '@vueuse/core';
|
||||
import { routerToFileWithPath, routerToName } from '@/utils/router';
|
||||
import { stopProcess } from '@/api/modules/process';
|
||||
import { loadTopCPU, loadTopMem } from '@/api/modules/dashboard';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
const hasFinePointer = useMediaQuery('(hover: hover) and (pointer: fine)');
|
||||
const showMore = ref(false);
|
||||
const totalCount = ref();
|
||||
|
||||
@@ -852,4 +885,12 @@ defineExpose({
|
||||
grid-column: span 3;
|
||||
}
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
:deep(.dashboard-status-popover) {
|
||||
max-width: calc(100vw - 24px);
|
||||
max-height: calc(100vh - 24px);
|
||||
max-height: calc(100dvh - 24px);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<template>
|
||||
<el-card class="shadow-sm">
|
||||
<el-card class="disk-card shadow-sm">
|
||||
<div class="border-b pb-4">
|
||||
<div class="flex items-center space-x-4">
|
||||
<div>
|
||||
<h3 class="text-lg">
|
||||
{{ $t('home.disk') }}{{ $t('commons.table.name') }}: {{ diskInfo.device }}
|
||||
<h3 class="disk-title text-lg">
|
||||
<span class="disk-title__name">
|
||||
{{ $t('home.disk') }}{{ $t('commons.table.name') }}: {{ diskInfo.device }}
|
||||
</span>
|
||||
<el-tag size="small" type="warning" v-if="scope === 'system'">
|
||||
{{ $t('disk.systemDisk') }}
|
||||
</el-tag>
|
||||
@@ -20,26 +22,28 @@
|
||||
{{ $t('disk.unpartitionedDisk') }}
|
||||
</el-tag>
|
||||
</h3>
|
||||
<div class="flex items-center space-x-6 text-sm">
|
||||
<el-text type="info">{{ $t('container.size') }}: {{ diskInfo.size }}</el-text>
|
||||
<el-text type="info">
|
||||
<div class="disk-summary text-sm">
|
||||
<el-text type="info" class="disk-summary__item">
|
||||
{{ $t('container.size') }}: {{ diskInfo.size }}
|
||||
</el-text>
|
||||
<el-text type="info" class="disk-summary__item">
|
||||
{{ $t('disk.partition') }}:
|
||||
<span v-if="diskInfo.partitions">
|
||||
{{ diskInfo.partitions?.length }}
|
||||
</span>
|
||||
<span v-else>0</span>
|
||||
</el-text>
|
||||
<el-text type="info" v-if="diskInfo.diskType" class="flex items-center">
|
||||
<el-text type="info" v-if="diskInfo.diskType" class="disk-summary__item">
|
||||
{{ $t('disk.diskType') }}:
|
||||
<el-tag class="ml-2" size="small" type="info">{{ diskInfo.diskType }}</el-tag>
|
||||
</el-text>
|
||||
<el-text type="info" v-if="diskInfo.model" class="flex items-center">
|
||||
<el-text type="info" v-if="diskInfo.model" class="disk-summary__item">
|
||||
{{ $t('disk.model') }}:
|
||||
<span class="ml-2">{{ diskInfo.model }}</span>
|
||||
<span class="disk-summary__value ml-2">{{ diskInfo.model }}</span>
|
||||
</el-text>
|
||||
<el-text type="info" v-if="diskInfo.serial" class="flex items-center">
|
||||
<el-text type="info" v-if="diskInfo.serial" class="disk-summary__item">
|
||||
{{ $t('disk.serial') }}:
|
||||
<span class="ml-2">{{ diskInfo.serial }}</span>
|
||||
<span class="disk-summary__value ml-2">{{ diskInfo.serial }}</span>
|
||||
</el-text>
|
||||
<div
|
||||
v-if="
|
||||
@@ -63,16 +67,20 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="diskInfo.partitions && diskInfo.partitions.length > 0">
|
||||
<el-table :data="diskInfo.partitions" class="w-full">
|
||||
<el-table-column prop="device" :label="$t('disk.partition') + $t('commons.table.name')" min-width="100">
|
||||
<el-table :data="diskInfo.partitions" class="w-full" :scrollbar-always-on="isMobile">
|
||||
<el-table-column
|
||||
prop="device"
|
||||
:label="$t('disk.partition') + $t('commons.table.name')"
|
||||
:min-width="columnMinWidth(100, 120)"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span class="font-medium">{{ row.device.split('/').pop() }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="size" :label="$t('container.size')" min-width="40" />
|
||||
<el-table-column prop="used" :label="$t('home.used')" min-width="40" />
|
||||
<el-table-column prop="avail" :label="$t('home.available')" min-width="40" />
|
||||
<el-table-column prop="usePercent" :label="$t('home.percent')" min-width="60">
|
||||
<el-table-column prop="size" :label="$t('container.size')" :min-width="columnMinWidth(40, 80)" />
|
||||
<el-table-column prop="used" :label="$t('home.used')" :min-width="columnMinWidth(40, 80)" />
|
||||
<el-table-column prop="avail" :label="$t('home.available')" :min-width="columnMinWidth(40, 80)" />
|
||||
<el-table-column prop="usePercent" :label="$t('home.percent')" :min-width="columnMinWidth(60, 120)">
|
||||
<template #default="{ row }">
|
||||
<el-progress
|
||||
:percentage="row.usePercent"
|
||||
@@ -82,7 +90,7 @@
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="mountPoint" :label="$t('disk.mountPoint')" min-width="120">
|
||||
<el-table-column prop="mountPoint" :label="$t('disk.mountPoint')" :min-width="columnMinWidth(120, 160)">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.mountPoint != ''">
|
||||
{{ row.mountPoint }}
|
||||
@@ -90,7 +98,7 @@
|
||||
<el-tag v-else size="small" type="warning">{{ $t('disk.unmounted') }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="filesystem" :label="$t('disk.filesystem')" min-width="80">
|
||||
<el-table-column prop="filesystem" :label="$t('disk.filesystem')" :min-width="columnMinWidth(80, 100)">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" type="info" v-if="row.filesystem != ''">{{ row.filesystem }}</el-tag>
|
||||
</template>
|
||||
@@ -117,16 +125,20 @@
|
||||
<el-text v-if="scope === 'system'">{{ $t('disk.systemDiskHelper') }}</el-text>
|
||||
</div>
|
||||
<div v-if="diskInfo.partitions == undefined && diskInfo.mountPoint != ''">
|
||||
<el-table :data="[diskInfo]" class="w-full">
|
||||
<el-table-column prop="device" :label="$t('disk.partition') + $t('commons.table.name')" min-width="100">
|
||||
<el-table :data="[diskInfo]" class="w-full" :scrollbar-always-on="isMobile">
|
||||
<el-table-column
|
||||
prop="device"
|
||||
:label="$t('disk.partition') + $t('commons.table.name')"
|
||||
:min-width="columnMinWidth(100, 120)"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span class="font-medium">{{ row.device.split('/').pop() }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="size" :label="$t('container.size')" min-width="40" />
|
||||
<el-table-column prop="used" :label="$t('home.used')" min-width="40" />
|
||||
<el-table-column prop="avail" :label="$t('home.available')" min-width="40" />
|
||||
<el-table-column prop="usePercent" :label="$t('home.percent')" min-width="60">
|
||||
<el-table-column prop="size" :label="$t('container.size')" :min-width="columnMinWidth(40, 80)" />
|
||||
<el-table-column prop="used" :label="$t('home.used')" :min-width="columnMinWidth(40, 80)" />
|
||||
<el-table-column prop="avail" :label="$t('home.available')" :min-width="columnMinWidth(40, 80)" />
|
||||
<el-table-column prop="usePercent" :label="$t('home.percent')" :min-width="columnMinWidth(60, 120)">
|
||||
<template #default="{ row }">
|
||||
<el-progress
|
||||
:percentage="row.usePercent"
|
||||
@@ -136,7 +148,7 @@
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="mountPoint" :label="$t('disk.mountPoint')" min-width="120">
|
||||
<el-table-column prop="mountPoint" :label="$t('disk.mountPoint')" :min-width="columnMinWidth(120, 160)">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.mountPoint != ''">
|
||||
{{ row.mountPoint }}
|
||||
@@ -144,7 +156,7 @@
|
||||
<el-tag v-else size="small" type="warning">{{ $t('disk.unmounted') }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="filesystem" :label="$t('disk.filesystem')" min-width="80">
|
||||
<el-table-column prop="filesystem" :label="$t('disk.filesystem')" :min-width="columnMinWidth(80, 100)">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" type="info" v-if="row.filesystem != ''">{{ row.filesystem }}</el-tag>
|
||||
</template>
|
||||
@@ -177,8 +189,10 @@ import { Host } from '@/api/interface/host';
|
||||
import i18n from '@/lang';
|
||||
import { unmountDisk } from '@/api/modules/host';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const emit = defineEmits(['partition', 'search', 'mount']);
|
||||
const { isMobile } = useGlobalStore();
|
||||
|
||||
defineProps({
|
||||
diskInfo: {
|
||||
@@ -195,6 +209,8 @@ const handlePartition = (diskInfo: Host.DiskInfo) => {
|
||||
emit('partition', diskInfo);
|
||||
};
|
||||
|
||||
const columnMinWidth = (desktop: number, mobile: number) => (isMobile.value ? mobile : desktop);
|
||||
|
||||
const mount = (diskInfo: Host.DiskInfo) => {
|
||||
emit('mount', diskInfo);
|
||||
};
|
||||
@@ -217,3 +233,49 @@ const unmount = (diskInfo: Host.DiskInfo) => {
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.disk-title {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.disk-title__name,
|
||||
.disk-summary__value {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.disk-summary {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 24px;
|
||||
}
|
||||
|
||||
.disk-summary__item {
|
||||
display: inline-flex;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.disk-card {
|
||||
--el-card-padding: 12px;
|
||||
}
|
||||
|
||||
.disk-summary {
|
||||
align-items: flex-start;
|
||||
gap: 8px 12px;
|
||||
}
|
||||
|
||||
.disk-summary__item {
|
||||
flex: 1 1 140px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -176,9 +176,9 @@
|
||||
</el-icon>
|
||||
<span class="sm:inline hidden pl-1">{{ $t('commons.button.refresh') }}</span>
|
||||
</el-text>
|
||||
<el-divider direction="vertical" v-if="!isMobile" class="!mx-0" />
|
||||
<el-dropdown @command="handleCreate" v-if="!isMobile" trigger="click">
|
||||
<el-text size="small">
|
||||
<el-divider direction="vertical" class="!mx-0" />
|
||||
<el-dropdown @command="handleCreate" trigger="click">
|
||||
<el-text size="small" class="cursor-pointer">
|
||||
{{ $t('commons.button.create') }}
|
||||
<el-icon><arrow-down /></el-icon>
|
||||
</el-text>
|
||||
@@ -281,6 +281,69 @@
|
||||
<small :title="node.label" class="tree-node-label">{{ node.label }}</small>
|
||||
</template>
|
||||
</span>
|
||||
<span
|
||||
v-if="isMobile && data.id !== 'new-dir' && data.id !== 'new-file'"
|
||||
class="tree-node-mobile-actions"
|
||||
@click.stop
|
||||
>
|
||||
<el-dropdown
|
||||
trigger="click"
|
||||
@command="(command) => handleMobileTreeCommand(command, data, node)"
|
||||
>
|
||||
<el-button
|
||||
class="mobile-tree-node-action"
|
||||
link
|
||||
:aria-label="$t('tabs.more')"
|
||||
>
|
||||
<el-icon><MoreFilled /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<fu-dropdown-item
|
||||
v-if="data.isDir"
|
||||
v-permission
|
||||
v-node-admin
|
||||
command="dir"
|
||||
>
|
||||
<svg-icon
|
||||
class="tree-context-menu__icon"
|
||||
iconName="p-file-folder"
|
||||
></svg-icon>
|
||||
{{ $t('file.dir') }}
|
||||
</fu-dropdown-item>
|
||||
<fu-dropdown-item
|
||||
v-if="data.isDir"
|
||||
v-permission
|
||||
v-node-admin
|
||||
command="file"
|
||||
>
|
||||
<svg-icon
|
||||
class="tree-context-menu__icon"
|
||||
iconName="p-file-normal"
|
||||
></svg-icon>
|
||||
{{ $t('menu.files') }}
|
||||
</fu-dropdown-item>
|
||||
<el-dropdown-item command="copy">
|
||||
<el-icon><CopyDocument /></el-icon>
|
||||
{{ $t('file.copyDir') }}
|
||||
</el-dropdown-item>
|
||||
<fu-dropdown-item v-permission v-node-admin command="rename">
|
||||
<el-icon><Edit /></el-icon>
|
||||
{{ $t('file.rename') }}
|
||||
</fu-dropdown-item>
|
||||
<fu-dropdown-item
|
||||
v-permission
|
||||
v-node-admin
|
||||
command="delete"
|
||||
divided
|
||||
>
|
||||
<el-icon><Delete /></el-icon>
|
||||
{{ $t('commons.button.delete') }}
|
||||
</fu-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</span>
|
||||
</template>
|
||||
</el-tree-v2>
|
||||
<div
|
||||
@@ -504,7 +567,7 @@ import { copyText } from '@/utils/clipboard';
|
||||
import { getIcon } from '@/utils/file';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import { TreeNodeData } from 'element-plus/es/components/tree-v2/src/types';
|
||||
import { CopyDocument, Delete, Edit, Refresh, Top } from '@element-plus/icons-vue';
|
||||
import { CopyDocument, Delete, Edit, MoreFilled, Refresh, Top } from '@element-plus/icons-vue';
|
||||
import { loadBaseDir } from '@/api/modules/setting';
|
||||
import CodeTabs from './tabs/index.vue';
|
||||
import FileHistoryDrawer from './history/index.vue';
|
||||
@@ -1841,6 +1904,23 @@ const deleteFromContextMenu = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleMobileTreeCommand = (command: string, data: any, node: any) => {
|
||||
treeContextMenu.data = data;
|
||||
treeContextMenu.node = node;
|
||||
if (command === 'dir' || command === 'file') {
|
||||
return createFromContextMenu(command);
|
||||
}
|
||||
if (command === 'copy') {
|
||||
return copyPathFromContextMenu();
|
||||
}
|
||||
if (command === 'rename') {
|
||||
return renameFromContextMenu();
|
||||
}
|
||||
if (command === 'delete') {
|
||||
return deleteFromContextMenu();
|
||||
}
|
||||
};
|
||||
|
||||
const currentEditingNode = ref<any>(null);
|
||||
|
||||
const createNewNode = (command: string) => {
|
||||
@@ -2157,7 +2237,8 @@ defineExpose({ acceptParams });
|
||||
.tree-node-content {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
flex: 1 1 auto;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@@ -2171,12 +2252,26 @@ defineExpose({ acceptParams });
|
||||
}
|
||||
|
||||
.tree-node-label {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tree-node-mobile-actions {
|
||||
flex: 0 0 32px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.mobile-tree-node-action {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex: 0 0 32px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.el-tabs) {
|
||||
--el-tabs-header-height: 29px;
|
||||
.el-tabs__header {
|
||||
@@ -2231,7 +2326,7 @@ defineExpose({ acceptParams });
|
||||
border-left: 3px solid var(--el-color-primary);
|
||||
}
|
||||
|
||||
@media (max-width: 599px) {
|
||||
@media (max-width: 767px) {
|
||||
.code-header {
|
||||
min-height: 44px;
|
||||
padding-right: 12px !important;
|
||||
|
||||
@@ -114,7 +114,7 @@ const onDropdownVisibleChange = (visible: boolean, currentPath: string) => {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 599px) {
|
||||
@media (max-width: 767px) {
|
||||
.el-dropdown-link {
|
||||
max-width: 112px;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<div class="content-container__search">
|
||||
<el-card>
|
||||
<div :class="isMobile ? 'flx-wrap' : 'flex justify-between'">
|
||||
<div class="monitor-toolbar monitor-toolbar--global">
|
||||
<el-date-picker
|
||||
@change="searchGlobal()"
|
||||
v-model="timeRangeGlobal"
|
||||
@@ -13,10 +13,10 @@
|
||||
:start-placeholder="$t('commons.search.timeStart')"
|
||||
:end-placeholder="$t('commons.search.timeEnd')"
|
||||
:shortcuts="shortcuts"
|
||||
style="max-width: 360px; width: 100%"
|
||||
class="monitor-time-range"
|
||||
:size="isMobile ? 'small' : 'default'"
|
||||
></el-date-picker>
|
||||
<TableRefresh class="float-right" @search="searchGlobal()" />
|
||||
<TableRefresh class="monitor-refresh float-right" @search="searchGlobal()" />
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
@@ -24,7 +24,7 @@
|
||||
<el-col :span="24">
|
||||
<el-card style="overflow: inherit">
|
||||
<template #header>
|
||||
<div :class="isMobile ? 'flx-wrap' : 'flex justify-between'">
|
||||
<div class="monitor-toolbar monitor-toolbar--card">
|
||||
<span class="title">{{ $t('monitor.avgLoad') }}</span>
|
||||
<el-date-picker
|
||||
@change="search('load')"
|
||||
@@ -34,7 +34,7 @@
|
||||
:start-placeholder="$t('commons.search.timeStart')"
|
||||
:end-placeholder="$t('commons.search.timeEnd')"
|
||||
:shortcuts="shortcuts"
|
||||
style="max-width: 360px; width: 100%"
|
||||
class="monitor-time-range"
|
||||
:size="isMobile ? 'small' : 'default'"
|
||||
></el-date-picker>
|
||||
</div>
|
||||
@@ -56,7 +56,7 @@
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
<el-card style="overflow: inherit">
|
||||
<template #header>
|
||||
<div :class="isMobile ? 'flx-wrap' : 'flex justify-between'">
|
||||
<div class="monitor-toolbar monitor-toolbar--card">
|
||||
<span class="title">CPU</span>
|
||||
<el-date-picker
|
||||
@change="search('cpu')"
|
||||
@@ -66,7 +66,7 @@
|
||||
:start-placeholder="$t('commons.search.timeStart')"
|
||||
:end-placeholder="$t('commons.search.timeEnd')"
|
||||
:shortcuts="shortcuts"
|
||||
style="max-width: 360px; width: 100%"
|
||||
class="monitor-time-range"
|
||||
:size="isMobile ? 'small' : 'default'"
|
||||
></el-date-picker>
|
||||
</div>
|
||||
@@ -86,7 +86,7 @@
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
<el-card style="overflow: inherit">
|
||||
<template #header>
|
||||
<div :class="isMobile ? 'flx-wrap' : 'flex justify-between'">
|
||||
<div class="monitor-toolbar monitor-toolbar--card">
|
||||
<span class="title">{{ $t('monitor.memory') }}</span>
|
||||
<el-date-picker
|
||||
@change="search('memory')"
|
||||
@@ -96,7 +96,7 @@
|
||||
:start-placeholder="$t('commons.search.timeStart')"
|
||||
:end-placeholder="$t('commons.search.timeEnd')"
|
||||
:shortcuts="shortcuts"
|
||||
style="max-width: 360px; width: 100%"
|
||||
class="monitor-time-range"
|
||||
:size="isMobile ? 'small' : 'default'"
|
||||
></el-date-picker>
|
||||
</div>
|
||||
@@ -118,7 +118,7 @@
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
<el-card style="overflow: inherit">
|
||||
<template #header>
|
||||
<div :class="isMobile ? 'flx-wrap' : 'flex justify-between'">
|
||||
<div class="monitor-toolbar monitor-toolbar--card">
|
||||
<div>
|
||||
<span class="title">{{ $t('monitor.disk') }} I/O{{ $t('commons.colon') }}</span>
|
||||
<el-dropdown max-height="300px">
|
||||
@@ -147,7 +147,7 @@
|
||||
:start-placeholder="$t('commons.search.timeStart')"
|
||||
:end-placeholder="$t('commons.search.timeEnd')"
|
||||
:shortcuts="shortcuts"
|
||||
style="max-width: 360px; width: 100%"
|
||||
class="monitor-time-range"
|
||||
:size="isMobile ? 'small' : 'default'"
|
||||
></el-date-picker>
|
||||
</div>
|
||||
@@ -167,7 +167,7 @@
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
|
||||
<el-card style="overflow: inherit">
|
||||
<template #header>
|
||||
<div :class="isMobile ? 'flx-wrap' : 'flex justify-between'">
|
||||
<div class="monitor-toolbar monitor-toolbar--card">
|
||||
<div>
|
||||
<span class="title">{{ $t('monitor.network') }}{{ $t('commons.colon') }}</span>
|
||||
<el-dropdown max-height="300px">
|
||||
@@ -196,7 +196,7 @@
|
||||
:start-placeholder="$t('commons.search.timeStart')"
|
||||
:end-placeholder="$t('commons.search.timeEnd')"
|
||||
:shortcuts="shortcuts"
|
||||
style="max-width: 360px; width: 100%"
|
||||
class="monitor-time-range"
|
||||
:size="isMobile ? 'small' : 'default'"
|
||||
></el-date-picker>
|
||||
</div>
|
||||
@@ -218,7 +218,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { ref, reactive, onMounted, watch } from 'vue';
|
||||
import { loadMonitor, getNetworkOptions, getIOOptions } from '@/api/modules/host';
|
||||
import { computeSize, computeSizeFromKBs } from '@/utils/size';
|
||||
import { dateFormatWithoutYear } from '@/utils/date';
|
||||
@@ -241,7 +241,13 @@ const networkChoose = ref();
|
||||
const netOptions = ref();
|
||||
const ioChoose = ref();
|
||||
const ioOptions = ref();
|
||||
const chartsOption = ref({ loadLoadChart: null, loadCPUChart: null, loadMemoryChart: null, loadNetworkChart: null });
|
||||
const chartsOption = ref<Record<string, any>>({
|
||||
loadLoadChart: null,
|
||||
loadCPUChart: null,
|
||||
loadMemoryChart: null,
|
||||
loadIOChart: null,
|
||||
loadNetworkChart: null,
|
||||
});
|
||||
const loadingMap = reactive({
|
||||
load: false,
|
||||
cpu: false,
|
||||
@@ -250,6 +256,62 @@ const loadingMap = reactive({
|
||||
network: false,
|
||||
});
|
||||
|
||||
const getLoadChartGrid = () =>
|
||||
isMobile.value ? { left: 8, right: 8, top: 64, bottom: '20%', containLabel: true } : undefined;
|
||||
const getMultiSeriesChartLegend = () =>
|
||||
isMobile.value
|
||||
? { show: true, type: 'scroll', orient: 'horizontal', left: 8, right: 8, top: 0, bottom: null }
|
||||
: { show: true, type: 'plain', orient: 'horizontal', left: 'center', top: null, bottom: 15 };
|
||||
const getTransferChartGrid = () =>
|
||||
isMobile.value
|
||||
? { left: 8, right: 8, top: 64, bottom: 110, containLabel: true }
|
||||
: { left: getSideWidth(true), right: getSideWidth(true), bottom: '20%' };
|
||||
const getSingleSeriesChartGrid = () =>
|
||||
isMobile.value ? { left: 8, right: 8, top: 40, bottom: 110, containLabel: true } : undefined;
|
||||
const getSingleSeriesChartLegend = () =>
|
||||
isMobile.value
|
||||
? { show: true, type: 'plain', top: 0, bottom: null, left: 'center', right: null }
|
||||
: { show: true, type: 'plain', top: null, bottom: 15, left: 'center' };
|
||||
const getMobileChartXAxis = () =>
|
||||
isMobile.value ? { axisLabel: { interval: 'auto', hideOverlap: true, fontSize: 10 } } : undefined;
|
||||
|
||||
watch(isMobile, () => {
|
||||
const loadOption = chartsOption.value.loadLoadChart;
|
||||
if (loadOption) {
|
||||
chartsOption.value.loadLoadChart = {
|
||||
...loadOption,
|
||||
grid: getLoadChartGrid(),
|
||||
legend: getMultiSeriesChartLegend(),
|
||||
};
|
||||
}
|
||||
|
||||
for (const chartKey of ['loadCPUChart', 'loadMemoryChart']) {
|
||||
const chartOption = chartsOption.value[chartKey];
|
||||
if (!chartOption) {
|
||||
continue;
|
||||
}
|
||||
chartsOption.value[chartKey] = {
|
||||
...chartOption,
|
||||
grid: getSingleSeriesChartGrid(),
|
||||
legend: getSingleSeriesChartLegend(),
|
||||
xAxis: getMobileChartXAxis(),
|
||||
};
|
||||
}
|
||||
|
||||
for (const chartKey of ['loadIOChart', 'loadNetworkChart']) {
|
||||
const chartOption = chartsOption.value[chartKey];
|
||||
if (!chartOption) {
|
||||
continue;
|
||||
}
|
||||
chartsOption.value[chartKey] = {
|
||||
...chartOption,
|
||||
grid: getTransferChartGrid(),
|
||||
legend: getMultiSeriesChartLegend(),
|
||||
xAxis: getMobileChartXAxis(),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const searchTime = ref();
|
||||
const searchInfo = reactive<Host.MonitorSearch>({
|
||||
param: '',
|
||||
@@ -443,7 +505,8 @@ function initLoadCharts(item: Host.MonitorData) {
|
||||
alignTicks: true,
|
||||
},
|
||||
],
|
||||
grid: isMobile.value ? { left: '15%', right: '15%', bottom: '20%' } : null,
|
||||
grid: getLoadChartGrid(),
|
||||
legend: getMultiSeriesChartLegend(),
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: function (datas: any) {
|
||||
@@ -472,7 +535,9 @@ function initCPUCharts(baseDate: any, items: Host.MonitorData) {
|
||||
return withCPUProcess(datas);
|
||||
},
|
||||
},
|
||||
|
||||
grid: getSingleSeriesChartGrid(),
|
||||
legend: getSingleSeriesChartLegend(),
|
||||
xAxis: getMobileChartXAxis(),
|
||||
formatStr: '%',
|
||||
};
|
||||
}
|
||||
@@ -496,7 +561,9 @@ function initMemCharts(baseDate: any, items: Host.MonitorData) {
|
||||
return withMemProcess(datas);
|
||||
},
|
||||
},
|
||||
|
||||
grid: getSingleSeriesChartGrid(),
|
||||
legend: getSingleSeriesChartLegend(),
|
||||
xAxis: getMobileChartXAxis(),
|
||||
formatStr: '%',
|
||||
};
|
||||
}
|
||||
@@ -537,11 +604,9 @@ function initNetCharts(item: Host.MonitorData) {
|
||||
return res;
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
left: getSideWidth(true),
|
||||
right: getSideWidth(true),
|
||||
bottom: '20%',
|
||||
},
|
||||
grid: getTransferChartGrid(),
|
||||
legend: getMultiSeriesChartLegend(),
|
||||
xAxis: getMobileChartXAxis(),
|
||||
formatStr: 'KB/s',
|
||||
};
|
||||
}
|
||||
@@ -610,7 +675,9 @@ function initIOCharts(item: Host.MonitorData) {
|
||||
return res;
|
||||
},
|
||||
},
|
||||
grid: { left: getSideWidth(true), right: getSideWidth(true), bottom: '20%' },
|
||||
grid: getTransferChartGrid(),
|
||||
legend: getMultiSeriesChartLegend(),
|
||||
xAxis: getMobileChartXAxis(),
|
||||
yAxis: [
|
||||
{ type: 'value', name: '( KB/s )', axisLabel: { fontSize: 10 } },
|
||||
{
|
||||
@@ -777,8 +844,54 @@ onMounted(() => {
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
min-width: 0;
|
||||
}
|
||||
.el-dropdown {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.monitor-toolbar {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
|
||||
> :first-child {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.monitor-toolbar :deep(.monitor-time-range) {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 360px;
|
||||
flex: 0 1 360px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1024px) {
|
||||
.monitor-toolbar--card {
|
||||
flex-wrap: wrap;
|
||||
|
||||
:deep(.monitor-time-range) {
|
||||
max-width: none;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.monitor-toolbar--global {
|
||||
flex-wrap: wrap;
|
||||
|
||||
:deep(.monitor-time-range) {
|
||||
max-width: none;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
:deep(.monitor-refresh) {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<FireRouter />
|
||||
<LayoutContent :title="$t('menu.network', 2)" v-loading="processStore.netLoading">
|
||||
<template #rightToolBar>
|
||||
<div class="w-full flex justify-end items-center gap-5">
|
||||
<div class="network-toolbar w-full flex justify-end items-center gap-5">
|
||||
<el-select
|
||||
v-model="filters"
|
||||
:placeholder="$t('commons.table.status')"
|
||||
@@ -13,7 +13,7 @@
|
||||
collapse-tags-tooltip
|
||||
:max-collapse-tags="2"
|
||||
@change="search()"
|
||||
class="p-w-300"
|
||||
class="network-toolbar__filter"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in statusOptions"
|
||||
@@ -23,16 +23,19 @@
|
||||
/>
|
||||
</el-select>
|
||||
<TableSearch
|
||||
class="network-toolbar__field"
|
||||
@search="search()"
|
||||
:placeholder="$t('process.pid')"
|
||||
v-model:searchName="processStore.netSearch.processID"
|
||||
/>
|
||||
<TableSearch
|
||||
class="network-toolbar__field"
|
||||
@search="search()"
|
||||
:placeholder="$t('process.processName')"
|
||||
v-model:searchName="processStore.netSearch.processName"
|
||||
/>
|
||||
<TableSearch
|
||||
class="network-toolbar__field"
|
||||
@search="search()"
|
||||
:placeholder="$t('commons.table.port')"
|
||||
v-model:searchName="processStore.netSearch.port"
|
||||
@@ -41,14 +44,16 @@
|
||||
</template>
|
||||
|
||||
<template #main>
|
||||
<div class="!h-[900px]">
|
||||
<div class="network-table">
|
||||
<el-auto-resizer>
|
||||
<template #default="{ height, width }">
|
||||
<el-table-v2
|
||||
:fixed="isCompactTable"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
:width="width"
|
||||
:height="height"
|
||||
:scrollbar-always-on="isCompactTable"
|
||||
:sort-by="sortState"
|
||||
@column-sort="changeSort"
|
||||
/>
|
||||
@@ -68,6 +73,7 @@ import { SortBy, TableV2SortOrder, ElIcon } from 'element-plus';
|
||||
import { Filter } from '@element-plus/icons-vue';
|
||||
import i18n from '@/lang';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { useMediaQuery } from '@vueuse/core';
|
||||
|
||||
const statusOptions = [
|
||||
{ text: 'LISTEN', value: 'LISTEN' },
|
||||
@@ -78,6 +84,7 @@ const statusOptions = [
|
||||
];
|
||||
|
||||
const { currentNode } = useGlobalStore();
|
||||
const isCompactTable = useMediaQuery('(max-width: 1024px)');
|
||||
const processStore = ProcessStore();
|
||||
|
||||
const quickSearchName = (name: string) => {
|
||||
@@ -155,8 +162,8 @@ const columns = ref([
|
||||
const addr = rowData.localaddr;
|
||||
const addrStr = addr?.ip ? `${addr.ip}${addr.port > 0 ? ':' + addr.port : ''}` : '';
|
||||
const hasPort = addr?.port > 0;
|
||||
return h('div', { class: 'flex items-center gap-1' }, [
|
||||
h('span', {}, addrStr),
|
||||
return h('div', { class: 'flex items-center gap-1 min-w-0' }, [
|
||||
h('span', { class: 'truncate', title: addrStr }, addrStr),
|
||||
hasPort
|
||||
? h(
|
||||
ElIcon,
|
||||
@@ -181,7 +188,8 @@ const columns = ref([
|
||||
width: 350,
|
||||
cellRenderer: ({ rowData }) => {
|
||||
const addr = rowData.remoteaddr;
|
||||
return addr?.ip ? `${addr.ip}${addr.port > 0 ? ':' + addr.port : ''}` : '';
|
||||
const addrStr = addr?.ip ? `${addr.ip}${addr.port > 0 ? ':' + addr.port : ''}` : '';
|
||||
return h('span', { class: 'truncate', title: addrStr }, addrStr);
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -247,3 +255,56 @@ onUnmounted(() => {
|
||||
processStore.disconnect();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.network-toolbar {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.network-toolbar__filter {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.network-table {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 900px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1024px) {
|
||||
.network-toolbar {
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.network-toolbar__filter,
|
||||
.network-toolbar__field {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
max-width: 300px;
|
||||
flex: 1 1 220px;
|
||||
}
|
||||
|
||||
.network-toolbar__field {
|
||||
:deep(.search-button) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.network-table {
|
||||
height: clamp(420px, calc(100vh - 260px), 900px);
|
||||
height: clamp(420px, calc(100dvh - 260px), 900px);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.network-toolbar__filter,
|
||||
.network-toolbar__field {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
<FireRouter />
|
||||
<LayoutContent :title="$t('menu.process', 2)" v-loading="processStore.psLoading">
|
||||
<template #rightToolBar>
|
||||
<div class="w-full flex justify-end items-center gap-5">
|
||||
<div class="process-toolbar w-full flex justify-end items-center gap-5">
|
||||
<el-select
|
||||
v-model="filters"
|
||||
:placeholder="$t('commons.table.status')"
|
||||
clearable
|
||||
@change="search()"
|
||||
class="p-w-300"
|
||||
class="process-toolbar__filter"
|
||||
multiple
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
@@ -23,16 +23,19 @@
|
||||
/>
|
||||
</el-select>
|
||||
<TableSearch
|
||||
class="process-toolbar__field"
|
||||
@search="search()"
|
||||
:placeholder="$t('process.pid')"
|
||||
v-model:searchName="processStore.psSearch.pid"
|
||||
/>
|
||||
<TableSearch
|
||||
class="process-toolbar__field"
|
||||
@search="search()"
|
||||
:placeholder="$t('commons.table.name')"
|
||||
v-model:searchName="processStore.psSearch.name"
|
||||
/>
|
||||
<TableSearch
|
||||
class="process-toolbar__field"
|
||||
@search="search()"
|
||||
:placeholder="$t('commons.table.user')"
|
||||
v-model:searchName="processStore.psSearch.username"
|
||||
@@ -40,15 +43,17 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<div class="!h-[900px]">
|
||||
<div class="process-table">
|
||||
<el-auto-resizer>
|
||||
<template #default="{ height, width }">
|
||||
<el-table-v2
|
||||
:fixed="isCompactTable"
|
||||
@column-sort="changeSort"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
:width="width"
|
||||
:height="height"
|
||||
:scrollbar-always-on="isCompactTable"
|
||||
:sort-by="sortState"
|
||||
></el-table-v2>
|
||||
</template>
|
||||
@@ -73,8 +78,10 @@ import { ProcessStore } from '@/store';
|
||||
import { SortBy, TableV2SortOrder, ElButton } from 'element-plus';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import RuntimeDiagnostics from './diagnostics/index.vue';
|
||||
import { useMediaQuery } from '@vueuse/core';
|
||||
|
||||
const { currentNode } = useGlobalStore();
|
||||
const isCompactTable = useMediaQuery('(max-width: 1024px)');
|
||||
const processStore = ProcessStore();
|
||||
const permissionDirective = resolveDirective('permission');
|
||||
|
||||
@@ -306,3 +313,56 @@ onUnmounted(() => {
|
||||
processStore.disconnect();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.process-toolbar {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.process-toolbar__filter {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.process-table {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 900px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1024px) {
|
||||
.process-toolbar {
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.process-toolbar__filter,
|
||||
.process-toolbar__field {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
max-width: 300px;
|
||||
flex: 1 1 220px;
|
||||
}
|
||||
|
||||
.process-toolbar__field {
|
||||
:deep(.search-button) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.process-table {
|
||||
height: clamp(420px, calc(100vh - 260px), 900px);
|
||||
height: clamp(420px, calc(100dvh - 260px), 900px);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.process-toolbar__filter,
|
||||
.process-toolbar__field {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
/>
|
||||
<el-date-picker
|
||||
v-model="timeRange"
|
||||
class="p-w-360"
|
||||
class="host-log-time-range"
|
||||
type="datetimerange"
|
||||
range-separator="-"
|
||||
:start-placeholder="$t('commons.search.timeStart')"
|
||||
@@ -293,4 +293,12 @@ watch([keyword, priority, service], () => {
|
||||
border: 1px solid var(--el-border-color-darker);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
:deep(.host-log-time-range.el-date-editor) {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -27,10 +27,16 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
<ComplexTable :pagination-config="paginationConfig" @sort-change="search" @search="search" :data="data">
|
||||
<ComplexTable
|
||||
:pagination-config="paginationConfig"
|
||||
@sort-change="search"
|
||||
@search="search"
|
||||
:data="data"
|
||||
:scrollbar-always-on="isMobile"
|
||||
>
|
||||
<el-table-column
|
||||
:label="$t('commons.table.name')"
|
||||
:min-width="80"
|
||||
:min-width="isMobile ? 140 : 80"
|
||||
prop="name"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
@@ -98,9 +104,9 @@
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<fu-table-operations
|
||||
width="300px"
|
||||
:width="isMobile ? 100 : 300"
|
||||
:buttons="buttons"
|
||||
:ellipsis="10"
|
||||
:ellipsis="isMobile ? 0 : 10"
|
||||
:label="$t('commons.table.operate')"
|
||||
fix
|
||||
/>
|
||||
@@ -125,7 +131,7 @@ import { MsgSuccess } from '@/utils/message';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const { isProductPro, isFxplay, docsUrl } = useGlobalStore();
|
||||
const { isProductPro, isFxplay, docsUrl, isMobile } = useGlobalStore();
|
||||
const loading = ref();
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<LayoutContent v-loading="loading" v-if="!isRecordShow && !isSettingShow" :title="$t('toolbox.clam.clam')">
|
||||
<template #prompt>
|
||||
<el-alert type="info" :closable="false">
|
||||
<el-alert class="clam-helper-alert" type="info" :closable="false">
|
||||
<template #title>
|
||||
{{ $t('toolbox.clam.clamHelper') }}
|
||||
<el-link class="ml-1 text-xs" v-if="!isFxplay" @click="toDoc()" type="primary">
|
||||
@@ -52,6 +52,7 @@
|
||||
:class="{ mask: !clamStatus.isRunning }"
|
||||
v-if="!isSettingShow"
|
||||
:pagination-config="paginationConfig"
|
||||
:scrollbar-always-on="isMobile"
|
||||
v-model:selects="selects"
|
||||
@sort-change="search"
|
||||
@search="search"
|
||||
@@ -60,7 +61,7 @@
|
||||
<el-table-column type="selection" fix />
|
||||
<el-table-column
|
||||
:label="$t('commons.table.name')"
|
||||
:min-width="60"
|
||||
:min-width="isMobile ? 140 : 60"
|
||||
prop="name"
|
||||
sortable
|
||||
show-overflow-tooltip
|
||||
@@ -163,6 +164,7 @@
|
||||
:buttons="buttons"
|
||||
:ellipsis="10"
|
||||
:label="$t('commons.table.operate')"
|
||||
:fixed="isMobile ? false : 'right'"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
@@ -202,7 +204,7 @@ import { routerToFileWithPath, routerToName } from '@/utils/router';
|
||||
const loading = ref();
|
||||
const selects = ref<any>([]);
|
||||
|
||||
const { docsUrl, isFxplay, isProductPro } = useGlobalStore();
|
||||
const { docsUrl, isFxplay, isProductPro, isMobile } = useGlobalStore();
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'clam-page-size',
|
||||
@@ -394,3 +396,23 @@ onMounted(() => {
|
||||
search();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@media only screen and (max-width: 767px) {
|
||||
.clam-helper-alert {
|
||||
:deep(.el-alert__content),
|
||||
:deep(.el-alert__title) {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:deep(.el-alert__title) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
:deep(.el-link) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<el-popover
|
||||
v-if="dialogData.rowData.path.length >= 35"
|
||||
placement="top-start"
|
||||
trigger="hover"
|
||||
:trigger="hasFinePointer ? 'hover' : 'click'"
|
||||
width="250"
|
||||
:content="dialogData.rowData.path"
|
||||
>
|
||||
@@ -59,7 +59,7 @@
|
||||
<LayoutContent :title="$t('cronjob.record')" :reload="true">
|
||||
<template #rightToolBar>
|
||||
<el-date-picker
|
||||
class="mr-2.5"
|
||||
class="mr-2.5 record-time-range"
|
||||
@change="search(true)"
|
||||
v-model="timeRangeLoad"
|
||||
type="datetimerange"
|
||||
@@ -194,6 +194,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useMediaQuery } from '@vueuse/core';
|
||||
import i18n from '@/lang';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
@@ -204,6 +205,8 @@ import LogFile from '@/components/log/file/index.vue';
|
||||
import { cleanClamRecord, handleClamScan, searchClamRecord } from '@/api/modules/toolbox';
|
||||
import { routerToFileWithPath } from '@/utils/router';
|
||||
|
||||
const hasFinePointer = useMediaQuery('(hover: hover) and (pointer: fine)');
|
||||
|
||||
const loading = ref();
|
||||
const refresh = ref(false);
|
||||
const hasRecords = ref();
|
||||
@@ -344,6 +347,7 @@ defineExpose({
|
||||
<style lang="scss" scoped>
|
||||
.infinite-list {
|
||||
height: calc(100vh - 318px);
|
||||
height: calc(100dvh - 318px);
|
||||
.select-sign {
|
||||
&::before {
|
||||
float: left;
|
||||
@@ -392,4 +396,46 @@ defineExpose({
|
||||
min-width: 1200px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1024px) {
|
||||
.mainClass {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.mainRowClass {
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
|
||||
> .el-col {
|
||||
flex: 0 0 100%;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.infinite-list {
|
||||
height: 320px;
|
||||
height: clamp(220px, 38dvh, 360px);
|
||||
}
|
||||
|
||||
.descriptionWide,
|
||||
.description {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.page-item {
|
||||
float: none;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
:global(.record-time-range.el-date-editor) {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<template>
|
||||
<el-form-item :label="$t('app.app')" prop="appDetailID" :rules="Rules.requiredSelect">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-row :gutter="20" class="w-[calc(100%+20px)] gap-y-3 lg:w-auto lg:gap-y-0">
|
||||
<el-col :span="12" :xs="24" :sm="24" :md="12" class="min-w-0">
|
||||
<el-select
|
||||
v-model="runtime.appID"
|
||||
:disabled="mode === 'edit' || loadingVersion"
|
||||
@change="changeApp(runtime.appID)"
|
||||
class="p-w-200"
|
||||
class="w-full min-w-0 lg:!w-[200px]"
|
||||
>
|
||||
<el-option v-for="(app, index) in apps" :key="index" :label="app.name" :value="app.id"></el-option>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :span="12" :xs="24" :sm="24" :md="12" class="min-w-0">
|
||||
<el-select
|
||||
v-model="runtime.version"
|
||||
:disabled="loadingVersion"
|
||||
:loading="loadingVersion"
|
||||
@change="changeVersion()"
|
||||
class="p-w-200"
|
||||
class="w-full min-w-0 lg:!w-[200px]"
|
||||
>
|
||||
<el-option
|
||||
v-for="(version, index) in appVersions"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</el-form-item>
|
||||
<div v-if="appKey == 'node'">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="18">
|
||||
<el-col :span="18" :xs="24" :sm="24" :md="18">
|
||||
<el-form-item :label="$t('runtime.runScript')" prop="params.EXEC_SCRIPT">
|
||||
<el-select
|
||||
v-model="runtime.params['EXEC_SCRIPT']"
|
||||
@@ -43,7 +43,7 @@
|
||||
</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="6" :xs="24" :sm="24" :md="6">
|
||||
<el-form-item :label="$t('runtime.customScript')" prop="params.CUSTOM_SCRIPT">
|
||||
<el-switch
|
||||
v-model="runtime.params['CUSTOM_SCRIPT']"
|
||||
|
||||
@@ -34,13 +34,13 @@
|
||||
</el-form-item>
|
||||
<div v-if="runtime.resource === 'appstore'">
|
||||
<el-form-item :label="$t('app.app')" prop="appDetailID" :rules="Rules.requiredSelect">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-row :gutter="20" class="w-[calc(100%+20px)] gap-y-3 lg:w-auto lg:gap-y-0">
|
||||
<el-col :span="12" :xs="24" :sm="24" :md="12" class="min-w-0">
|
||||
<el-select
|
||||
v-model="runtime.appID"
|
||||
:disabled="mode === 'edit'"
|
||||
@change="changeApp(runtime.appID)"
|
||||
class="p-w-200"
|
||||
class="w-full min-w-0 lg:!w-[200px]"
|
||||
>
|
||||
<el-option
|
||||
v-for="(app, index) in apps"
|
||||
@@ -50,13 +50,14 @@
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :span="12" :xs="24" :sm="24" :md="12" class="min-w-0">
|
||||
<el-select v-model="runtime.version" @change="changeVersion()" class="p-w-200">
|
||||
<el-option
|
||||
v-for="(version, index) in appVersions"
|
||||
:key="index"
|
||||
:label="version"
|
||||
:value="version"
|
||||
class="w-full min-w-0 lg:!w-[200px]"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-col>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row :gutter="22" v-for="(domain, index) of create.domains" :key="index">
|
||||
<el-col :span="6">
|
||||
<el-row class="domain-create-row" :gutter="22" v-for="(domain, index) of create.domains" :key="index">
|
||||
<el-col class="domain-create-field domain-create-domain" :span="6" :xs="24">
|
||||
<el-form-item
|
||||
:label="index == 0 ? $t('website.domain') : ''"
|
||||
:prop="`domains.${index}.domain`"
|
||||
@@ -16,7 +16,7 @@
|
||||
<span class="input-help" v-if="domainWarnings[index]">{{ $t('website.domainNotFQDN') }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col class="domain-create-field domain-create-host" :span="6" :xs="24">
|
||||
<el-form-item :label="index == 0 ? $t('toolbox.device.hostname') : ''">
|
||||
<el-input
|
||||
type="string"
|
||||
@@ -26,7 +26,7 @@
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-col class="domain-create-field domain-create-port" :span="4" :xs="12">
|
||||
<el-form-item
|
||||
:label="index == 0 ? $t('commons.table.port') : ''"
|
||||
:prop="`domains.${index}.port`"
|
||||
@@ -35,24 +35,25 @@
|
||||
<el-input type="number" v-model.number="create.domains[index].port"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
<el-col class="domain-create-field domain-create-ssl" :span="2" :xs="6">
|
||||
<el-form-item :label="index == 0 ? 'SSL' : ''" :prop="`domains.${index}.ssl`">
|
||||
<el-checkbox
|
||||
class="domain-create-ssl-control"
|
||||
v-model="create.domains[index].ssl"
|
||||
:disabled="create.domains[index].port == 80"
|
||||
></el-checkbox>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4" v-if="index == 0">
|
||||
<el-col class="domain-create-field domain-create-operation" :span="4" :xs="6" v-if="index == 0">
|
||||
<el-form-item :label="$t('commons.table.operate')">
|
||||
<el-button v-permission @click="addDomain">
|
||||
<el-button v-permission :aria-label="$t('commons.button.add')" @click="addDomain">
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4" v-else>
|
||||
<el-col class="domain-create-field domain-create-operation" :span="4" :xs="6" v-else>
|
||||
<el-form-item>
|
||||
<el-button v-permission @click="removeDomain(index)">
|
||||
<el-button v-permission :aria-label="$t('commons.button.delete')" @click="removeDomain(index)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@@ -381,3 +382,42 @@ onMounted(() => {
|
||||
handleParams();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@media only screen and (max-width: 1024px) {
|
||||
.domain-create-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(64px, 84px) minmax(64px, 84px);
|
||||
}
|
||||
|
||||
.domain-create-field {
|
||||
flex: none;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.domain-create-domain,
|
||||
.domain-create-host {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.domain-create-port {
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
.domain-create-ssl {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.domain-create-operation {
|
||||
grid-column: 3;
|
||||
}
|
||||
|
||||
.domain-create-ssl-control {
|
||||
min-width: 40px;
|
||||
min-height: 32px;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user