首页接口重构
This commit is contained in:
@@ -1,21 +1,347 @@
|
||||
<template>
|
||||
<PageWrapper :contentFullHeight="true" :dense="true" title="false" contentClass="docker-page-wrapper">
|
||||
<div class="docker-page">
|
||||
<div class="docker-layout">
|
||||
<section class="docker-panel docker-hosts">
|
||||
<div class="panel-title">
|
||||
<span>主机列表</span>
|
||||
<span class="panel-title__sub">{{ serverOptions.length }} 台</span>
|
||||
</div>
|
||||
<div class="docker-hosts__body">
|
||||
<div class="host-list">
|
||||
<button
|
||||
v-for="item in serverOptions"
|
||||
:key="item.accountId"
|
||||
:class="['host-item', { 'host-item--active': selectedAccountId === item.accountId }]"
|
||||
type="button"
|
||||
@click="handleServerSelect(item)"
|
||||
>
|
||||
<div class="host-item__header">
|
||||
<div class="host-item__name">{{ item.hostName }}</div>
|
||||
<el-tag size="small" :type="getServerStatus(item).type">
|
||||
{{ getServerStatus(item).label }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="host-item__meta">{{ item.hostIp }}:{{ item.hostPort }}</div>
|
||||
<div class="host-item__user">{{ item.username }}</div>
|
||||
<div class="host-item__footer">
|
||||
<span>账号 {{ item.accountId }}</span>
|
||||
<span>{{ getServerStatus(item).label }}</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="docker-main">
|
||||
<section class="docker-panel docker-detail">
|
||||
<div class="panel-title">
|
||||
<span>主机详情</span>
|
||||
<span class="panel-title__sub">{{ currentServer?.hostName || '-' }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="currentServer" class="docker-detail__body">
|
||||
<div class="hero-card">
|
||||
<div class="hero-card__left">
|
||||
<div class="hero-card__title">{{ currentServer.hostName }}</div>
|
||||
<div class="hero-card__meta">{{ currentServer.hostIp }}:{{ currentServer.hostPort }}</div>
|
||||
<div class="hero-card__meta">账号:{{ currentServer.username }}</div>
|
||||
</div>
|
||||
<div class="hero-card__right">
|
||||
<div class="hero-card__badge">{{ getServerStatus(currentServer).label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-grid">
|
||||
<div class="metric-card metric-card--cpu">
|
||||
<div class="metric-card__label">CPU</div>
|
||||
<div class="metric-card__value">--</div>
|
||||
<div class="metric-card__progress">
|
||||
<span :style="{ width: '0%' }"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-card metric-card--memory">
|
||||
<div class="metric-card__label">内存</div>
|
||||
<div class="metric-card__value">--</div>
|
||||
<div class="metric-card__progress">
|
||||
<span :style="{ width: '0%' }"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-card metric-card--disk">
|
||||
<div class="metric-card__label">磁盘</div>
|
||||
<div class="metric-card__value">--</div>
|
||||
<div class="metric-card__progress">
|
||||
<span :style="{ width: '0%' }"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-grid">
|
||||
<div class="detail-card">
|
||||
<div class="detail-card__label">账号标识</div>
|
||||
<div class="detail-card__value">{{ currentServer.accountId }}</div>
|
||||
</div>
|
||||
<div class="detail-card">
|
||||
<div class="detail-card__label">容器总数</div>
|
||||
<div class="detail-card__value">{{ currentContainers.length }}</div>
|
||||
</div>
|
||||
<div class="detail-card">
|
||||
<div class="detail-card__label">运行中</div>
|
||||
<div class="detail-card__value">{{ runningCount }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-loading="loading" class="docker-panel docker-table">
|
||||
<div class="panel-title">
|
||||
<span>容器列表</span>
|
||||
<span class="panel-title__sub">{{ currentContainers.length }} 个容器</span>
|
||||
</div>
|
||||
|
||||
<div class="docker-table__body">
|
||||
<el-table :data="currentContainers" height="100%" :border="false" :show-header="true">
|
||||
<el-table-column prop="created" label="创建时间" width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="containerId" label="容器ID" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column prop="names" label="容器名称" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column prop="image" label="镜像名称" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column prop="status" label="运行状态" width="135" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getStatusTagType(row.status)">{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="ports" label="端口映射" min-width="145" show-overflow-tooltip />
|
||||
<el-table-column prop="command" label="启动命令" min-width="125" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="200" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="action-group">
|
||||
<el-tooltip
|
||||
v-if="isContainerStopped(row.status)"
|
||||
content="启动"
|
||||
placement="top"
|
||||
:show-after="200"
|
||||
>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
:icon="VideoPlay"
|
||||
:loading="actionLoadingMap[row.containerId] === 'start'"
|
||||
@click="handleDockerAction('start', row)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<template v-else>
|
||||
<el-tooltip content="重启" placement="top" :show-after="200">
|
||||
<el-button
|
||||
link
|
||||
type="warning"
|
||||
:icon="RefreshRight"
|
||||
:loading="actionLoadingMap[row.containerId] === 'restart'"
|
||||
@click="handleDockerAction('restart', row)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="停止" placement="top" :show-after="200">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
:icon="SwitchButton"
|
||||
:loading="actionLoadingMap[row.containerId] === 'stop'"
|
||||
@click="handleDockerAction('stop', row)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-tooltip content="详情" placement="top" :show-after="200">
|
||||
<el-button
|
||||
link
|
||||
type="info"
|
||||
:icon="View"
|
||||
:loading="detailLoadingMap[row.containerId] === 'inspect'"
|
||||
@click="handleDockerDetail('inspect', row)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="日志" placement="top" :show-after="200">
|
||||
<el-button
|
||||
link
|
||||
type="info"
|
||||
:icon="Tickets"
|
||||
:loading="detailLoadingMap[row.containerId] === 'logs'"
|
||||
@click="handleDockerDetail('logs', row)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="resultVisible" class="docker-info-dialog" :title="resultTitle" width="60%" destroy-on-close>
|
||||
<div class="result-dialog">
|
||||
<div class="result-dialog__header">
|
||||
<div class="result-dialog__title">{{ currentContainer?.names || '-' }}</div>
|
||||
<div class="result-dialog__header-divider"></div>
|
||||
<div class="result-dialog__time">
|
||||
<div class="result-dialog__time-left">
|
||||
<span>主机:{{ currentServer?.hostName || '-' }}</span>
|
||||
<span>容器ID:{{ currentContainer?.containerId || '-' }}</span>
|
||||
</div>
|
||||
<div class="result-dialog__time-right">{{ resultTitle }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider class="result-dialog__divider" />
|
||||
<div class="result-dialog__content-panel">
|
||||
<div class="result-dialog__content">{{ resultContent || '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</PageWrapper>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="Docker">
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { PageWrapper } from '@jeesite/core/components/Page';
|
||||
|
||||
import { RefreshRight, SwitchButton, Tickets, VideoPlay, View } from '@element-plus/icons-vue';
|
||||
import {
|
||||
ContainerInfo,
|
||||
DockerResult,
|
||||
ServerInfo,
|
||||
myContainerInfo,
|
||||
myDockerInspect,
|
||||
myDockerLogs,
|
||||
myDockerRestart,
|
||||
myDockerStart,
|
||||
myDockerStop,
|
||||
myServerInfo,
|
||||
} from '@jeesite/biz/api/biz/myDocker';
|
||||
|
||||
const loading = ref(false);
|
||||
const serverOptions = ref<ServerInfo[]>([]);
|
||||
const sourceData = ref<ContainerInfo[]>([]);
|
||||
const selectedAccountId = ref('');
|
||||
const resultVisible = ref(false);
|
||||
const resultTitle = ref('');
|
||||
const resultContent = ref('');
|
||||
const currentContainer = ref<ContainerInfo | null>(null);
|
||||
const actionLoadingMap = ref<Record<string, 'start' | 'stop' | 'restart' | ''>>({});
|
||||
const detailLoadingMap = ref<Record<string, 'logs' | 'inspect' | ''>>({});
|
||||
const containerQueryParams = reactive({
|
||||
accountId: '',
|
||||
});
|
||||
const containerActionParams = reactive({
|
||||
accountId: '',
|
||||
containerId: '',
|
||||
});
|
||||
|
||||
const currentServer = computed(
|
||||
() => serverOptions.value.find((item) => item.accountId === selectedAccountId.value) || null,
|
||||
);
|
||||
const currentContainers = computed(() => sourceData.value);
|
||||
const runningCount = computed(
|
||||
() => sourceData.value.filter((item) => getStatusTagType(item.status) === 'success').length,
|
||||
);
|
||||
|
||||
function handleServerSelect(item: ServerInfo) {
|
||||
const accountId = item.accountId || '';
|
||||
selectedAccountId.value = accountId;
|
||||
containerQueryParams.accountId = accountId;
|
||||
getContainerList();
|
||||
}
|
||||
|
||||
function getServerStatus(item?: ServerInfo | null) {
|
||||
const online = Boolean(item?.accountId);
|
||||
return {
|
||||
label: online ? '在线' : '离线',
|
||||
type: online ? 'success' : 'danger',
|
||||
} as const;
|
||||
}
|
||||
|
||||
function getStatusTagType(status?: string) {
|
||||
const value = String(status || '').toLowerCase();
|
||||
if (value.includes('up') || value.includes('running')) return 'success';
|
||||
if (value.includes('created') || value.includes('restart')) return 'warning';
|
||||
if (value.includes('exit') || value.includes('stop')) return 'danger';
|
||||
return 'info';
|
||||
}
|
||||
|
||||
function isContainerStopped(status?: string) {
|
||||
const value = String(status || '').toLowerCase();
|
||||
return value.includes('exit') || value.includes('stop') || value.includes('dead');
|
||||
}
|
||||
|
||||
async function getServerList() {
|
||||
try {
|
||||
serverOptions.value = (await myServerInfo()) || [];
|
||||
if (!selectedAccountId.value && serverOptions.value.length) {
|
||||
selectedAccountId.value = serverOptions.value[0].accountId || '';
|
||||
if (selectedAccountId.value) {
|
||||
containerQueryParams.accountId = selectedAccountId.value;
|
||||
getContainerList();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
serverOptions.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
async function getContainerList() {
|
||||
if (!containerQueryParams.accountId) {
|
||||
sourceData.value = [];
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
sourceData.value = (await myContainerInfo(containerQueryParams)) || [];
|
||||
} catch (error) {
|
||||
sourceData.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDockerAction(action: 'start' | 'stop' | 'restart', row: ContainerInfo) {
|
||||
actionLoadingMap.value[row.containerId] = action;
|
||||
try {
|
||||
containerActionParams.accountId = row.accountId || '';
|
||||
containerActionParams.containerId = row.containerId || '';
|
||||
const api = action === 'start' ? myDockerStart : action === 'stop' ? myDockerStop : myDockerRestart;
|
||||
const res = (await api(containerActionParams)) as DockerResult;
|
||||
ElMessage.success(res?.message || '操作成功');
|
||||
await getContainerList();
|
||||
} catch (error) {
|
||||
ElMessage.error('操作失败,请稍后重试');
|
||||
} finally {
|
||||
actionLoadingMap.value[row.containerId] = '';
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDockerDetail(type: 'logs' | 'inspect', row: ContainerInfo) {
|
||||
currentContainer.value = row;
|
||||
resultTitle.value = type === 'logs' ? '容器日志' : '容器详情';
|
||||
detailLoadingMap.value[row.containerId] = type;
|
||||
try {
|
||||
containerActionParams.accountId = row.accountId || '';
|
||||
containerActionParams.containerId = row.containerId || '';
|
||||
const api = type === 'logs' ? myDockerLogs : myDockerInspect;
|
||||
const res = await api(containerActionParams);
|
||||
resultContent.value = res?.output || res?.message || res?.error || '-';
|
||||
} catch (error) {
|
||||
resultContent.value = '获取内容失败,请稍后重试';
|
||||
}
|
||||
detailLoadingMap.value[row.containerId] = '';
|
||||
resultVisible.value = true;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await getServerList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@dark-bg: #141414;
|
||||
@desktop-page-gap: 12px;
|
||||
@desktop-page-padding: 0;
|
||||
@desktop-card-radius: 10px;
|
||||
@desktop-card-border: 1px solid rgb(226 232 240);
|
||||
@desktop-card-shadow: 0 1px 3px rgb(15 23 42 / 0.06);
|
||||
@desktop-dark-border: rgb(51 65 85);
|
||||
@analysis-dark-bg: rgb(20, 20, 20);
|
||||
@analysis-dark-shadow: 0 10px 24px rgb(0 0 0 / 24%);
|
||||
|
||||
.docker-page-wrapper {
|
||||
display: flex;
|
||||
@@ -42,4 +368,628 @@
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.docker-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 300px minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.docker-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
background: rgb(255, 255, 255);
|
||||
box-shadow: 0 8px 24px rgb(148 163 184 / 14%);
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 37px;
|
||||
padding: 8px 16px;
|
||||
border-bottom: 1px solid rgb(226 232 240);
|
||||
color: rgb(51 65 85);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
|
||||
&__sub {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: rgb(100 116 139);
|
||||
}
|
||||
}
|
||||
|
||||
.docker-hosts {
|
||||
&__body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.host-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.host-item {
|
||||
padding: 12px;
|
||||
border: 1px solid rgb(226 232 240);
|
||||
border-radius: 12px;
|
||||
background: radial-gradient(circle at top right, rgb(219 234 254 / 45%), transparent 38%), rgb(255, 255, 255);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
box-shadow 0.2s ease,
|
||||
transform 0.2s ease,
|
||||
background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: rgb(147 197 253);
|
||||
box-shadow: 0 12px 26px rgb(96 165 250 / 18%);
|
||||
}
|
||||
|
||||
&--active {
|
||||
border-color: rgb(191 219 254);
|
||||
background: radial-gradient(circle at top right, rgb(191 219 254 / 65%), transparent 42%), rgb(239 246 255);
|
||||
box-shadow: 0 14px 30px rgb(59 130 246 / 14%);
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: rgb(30 41 59);
|
||||
}
|
||||
|
||||
&__meta,
|
||||
&__user,
|
||||
&__footer {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(100 116 139);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid rgb(226 232 240);
|
||||
}
|
||||
}
|
||||
|
||||
.docker-main {
|
||||
display: grid;
|
||||
grid-template-rows: 360px minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.docker-detail {
|
||||
&__body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-card {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 12px;
|
||||
background:
|
||||
radial-gradient(circle at top left, rgb(191 219 254 / 75%), transparent 34%),
|
||||
linear-gradient(135deg, rgb(248 250 252) 0%, rgb(255 255 255) 100%);
|
||||
box-shadow: inset 0 0 0 1px rgb(226 232 240);
|
||||
|
||||
&__left {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
line-height: 24px;
|
||||
color: rgb(30 41 59);
|
||||
}
|
||||
|
||||
&__meta {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(100 116 139);
|
||||
}
|
||||
|
||||
&__badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 82px;
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
border-radius: 999px;
|
||||
background: rgb(30 64 175);
|
||||
color: rgb(239 246 255);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.metric-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
background: rgb(248 250 252);
|
||||
box-shadow: inset 0 0 0 1px rgb(226 232 240);
|
||||
|
||||
&__label {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(100 116 139);
|
||||
}
|
||||
|
||||
&__value {
|
||||
margin-top: 10px;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
color: rgb(30 41 59);
|
||||
}
|
||||
|
||||
&__progress {
|
||||
height: 8px;
|
||||
margin-top: 12px;
|
||||
border-radius: 999px;
|
||||
background: rgb(226 232 240);
|
||||
overflow: hidden;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
height: 100%;
|
||||
border-radius: 999px;
|
||||
}
|
||||
}
|
||||
|
||||
&--cpu .metric-card__progress span {
|
||||
background: linear-gradient(90deg, rgb(59 130 246), rgb(96 165 250));
|
||||
}
|
||||
|
||||
&--memory .metric-card__progress span {
|
||||
background: linear-gradient(90deg, rgb(16 185 129), rgb(52 211 153));
|
||||
}
|
||||
|
||||
&--disk .metric-card__progress span {
|
||||
background: linear-gradient(90deg, rgb(249 115 22), rgb(251 146 60));
|
||||
}
|
||||
}
|
||||
|
||||
.detail-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
padding: 12px;
|
||||
border-radius: 10px;
|
||||
background: rgb(248 250 252);
|
||||
box-shadow: inset 0 0 0 1px rgb(226 232 240);
|
||||
|
||||
&__label {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(100 116 139);
|
||||
}
|
||||
|
||||
&__value {
|
||||
margin-top: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: rgb(30 41 59);
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
||||
.docker-table {
|
||||
&__body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 8px 12px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.el-table {
|
||||
width: 100%;
|
||||
--el-table-border-color: transparent;
|
||||
--el-table-header-bg-color: rgb(255, 255, 255);
|
||||
--el-table-tr-bg-color: transparent;
|
||||
--el-table-row-hover-bg-color: rgb(241 245 249);
|
||||
--el-table-text-color: rgb(71 85 105);
|
||||
--el-table-header-text-color: rgb(51 65 85);
|
||||
background: transparent;
|
||||
|
||||
&::before,
|
||||
&::after,
|
||||
.el-table__inner-wrapper::before,
|
||||
.el-table__border-left-patch {
|
||||
display: none;
|
||||
}
|
||||
|
||||
th.el-table__cell,
|
||||
td.el-table__cell {
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
border-bottom: 1px solid rgb(226 232 240);
|
||||
}
|
||||
|
||||
th.el-table__cell {
|
||||
background: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
td.el-table__cell,
|
||||
th.el-table__cell {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.cell {
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 6px;
|
||||
|
||||
.el-button {
|
||||
padding: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.result-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: rgb(30 41 59);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__header-divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: rgb(226 232 240);
|
||||
}
|
||||
|
||||
&__time {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
font-size: 13px;
|
||||
color: rgb(100 116 139);
|
||||
}
|
||||
|
||||
&__time-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__time-right {
|
||||
flex-shrink: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&__divider {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
&__content-panel {
|
||||
padding: 4px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
background: rgb(255, 255, 255);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&__content {
|
||||
min-height: 220px;
|
||||
max-height: 320px;
|
||||
padding: 14px 16px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid rgb(191 219 254);
|
||||
border-radius: 10px;
|
||||
background: rgb(255 255 255);
|
||||
color: rgb(51 65 85);
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgb(191 219 254) transparent;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
border-radius: 999px;
|
||||
background: rgb(191 219 254);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.docker-info-dialog {
|
||||
.el-dialog {
|
||||
background: rgb(255 255 255) !important;
|
||||
--el-dialog-bg-color: rgb(255 255 255);
|
||||
--el-bg-color: rgb(255 255 255);
|
||||
box-shadow: 0 12px 32px rgb(15 23 42 / 18%);
|
||||
}
|
||||
|
||||
.el-dialog__header {
|
||||
margin-right: 0;
|
||||
padding: 20px 24px 16px;
|
||||
border-bottom: 1px solid rgb(226 232 240) !important;
|
||||
background: rgb(255 255 255) !important;
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
padding: 20px 24px 24px;
|
||||
background: rgb(255 255 255) !important;
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme='dark'] {
|
||||
.docker-panel {
|
||||
background: @analysis-dark-bg;
|
||||
box-shadow: @analysis-dark-shadow;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
color: rgb(203 213 225);
|
||||
border-bottom-color: rgb(51 65 85);
|
||||
|
||||
&__sub {
|
||||
color: rgb(148 163 184);
|
||||
}
|
||||
}
|
||||
|
||||
.host-item {
|
||||
border-color: rgb(51 65 85);
|
||||
background: radial-gradient(circle at top right, rgb(37 99 235 / 22%), transparent 36%), rgb(20, 20, 20);
|
||||
|
||||
&:hover {
|
||||
border-color: rgb(96 165 250);
|
||||
box-shadow: 0 10px 24px rgb(37 99 235 / 18%);
|
||||
}
|
||||
|
||||
&--active {
|
||||
border-color: rgb(59 130 246 / 45%);
|
||||
background: radial-gradient(circle at top right, rgb(37 99 235 / 30%), transparent 40%), rgb(20, 20, 20);
|
||||
box-shadow: 0 12px 26px rgb(37 99 235 / 16%);
|
||||
}
|
||||
|
||||
&__name {
|
||||
color: rgb(226 232 240);
|
||||
}
|
||||
|
||||
&__meta,
|
||||
&__user,
|
||||
&__footer {
|
||||
color: rgb(148 163 184);
|
||||
}
|
||||
|
||||
&__footer {
|
||||
border-top-color: rgb(51 65 85);
|
||||
}
|
||||
}
|
||||
|
||||
.hero-card {
|
||||
background:
|
||||
radial-gradient(circle at top left, rgb(37 99 235 / 26%), transparent 34%),
|
||||
linear-gradient(135deg, rgb(20, 20, 20) 0%, rgb(28 28 28) 100%);
|
||||
box-shadow: inset 0 0 0 1px rgb(51 65 85);
|
||||
|
||||
&__title {
|
||||
color: rgb(226 232 240);
|
||||
}
|
||||
|
||||
&__meta {
|
||||
color: rgb(148 163 184);
|
||||
}
|
||||
|
||||
&__badge {
|
||||
background: rgb(30 64 175 / 80%);
|
||||
color: rgb(219 234 254);
|
||||
}
|
||||
}
|
||||
|
||||
.metric-card,
|
||||
.detail-card {
|
||||
background: rgb(20, 20, 20);
|
||||
box-shadow: inset 0 0 0 1px rgb(51 65 85);
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
&__label {
|
||||
color: rgb(148 163 184);
|
||||
}
|
||||
|
||||
&__value {
|
||||
color: rgb(226 232 240);
|
||||
}
|
||||
|
||||
&__progress {
|
||||
background: rgb(51 65 85);
|
||||
}
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
&__label {
|
||||
color: rgb(148 163 184);
|
||||
}
|
||||
|
||||
&__value {
|
||||
color: rgb(226 232 240);
|
||||
}
|
||||
}
|
||||
|
||||
.docker-table {
|
||||
.el-table {
|
||||
--el-table-header-bg-color: rgb(20, 20, 20);
|
||||
--el-table-row-hover-bg-color: rgb(30 41 59);
|
||||
--el-table-text-color: rgb(148 163 184);
|
||||
--el-table-header-text-color: rgb(226 232 240);
|
||||
background: transparent;
|
||||
|
||||
th.el-table__cell,
|
||||
td.el-table__cell {
|
||||
border-bottom-color: rgb(51 65 85);
|
||||
}
|
||||
|
||||
th.el-table__cell {
|
||||
background: rgb(20, 20, 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.result-dialog {
|
||||
&__title {
|
||||
color: rgb(226 232 240);
|
||||
}
|
||||
|
||||
&__time {
|
||||
color: rgb(148 163 184);
|
||||
}
|
||||
|
||||
&__header-divider {
|
||||
background: rgb(51 65 85);
|
||||
}
|
||||
|
||||
&__content-panel {
|
||||
background: rgb(20, 20, 20);
|
||||
}
|
||||
|
||||
&__content {
|
||||
background: rgb(20, 20, 20);
|
||||
color: rgb(203 213 225);
|
||||
border-color: rgb(51 65 85);
|
||||
scrollbar-color: rgb(71 85 105) transparent;
|
||||
}
|
||||
|
||||
&__content::-webkit-scrollbar-thumb {
|
||||
background: rgb(71 85 105);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .docker-info-dialog {
|
||||
--el-bg-color: rgb(20, 20, 20);
|
||||
--el-dialog-bg-color: rgb(20, 20, 20);
|
||||
--el-fill-color-blank: rgb(20, 20, 20);
|
||||
|
||||
.el-dialog {
|
||||
background: rgb(20, 20, 20) !important;
|
||||
--el-dialog-bg-color: rgb(20, 20, 20);
|
||||
--el-bg-color: rgb(20, 20, 20);
|
||||
--el-fill-color-blank: rgb(20, 20, 20);
|
||||
box-shadow: 0 14px 36px rgb(0 0 0 / 42%);
|
||||
}
|
||||
|
||||
.el-dialog__wrapper,
|
||||
.el-overlay-dialog,
|
||||
.el-dialog__content {
|
||||
background: rgb(20, 20, 20) !important;
|
||||
}
|
||||
|
||||
.el-dialog__header {
|
||||
border-bottom: 1px solid rgb(51 65 85) !important;
|
||||
background: rgb(20, 20, 20) !important;
|
||||
}
|
||||
|
||||
.el-dialog__title {
|
||||
color: rgb(226 232 240);
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
background: rgb(20, 20, 20) !important;
|
||||
}
|
||||
|
||||
.el-dialog__headerbtn .el-dialog__close {
|
||||
color: rgb(148 163 184);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.docker-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.docker-main {
|
||||
grid-template-rows: 400px minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.detail-grid,
|
||||
.metric-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user