首页接口重构
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package com.jeesite.modules.apps.Module;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class SystemInfo implements Serializable {
|
||||
private String cpu; // CPU使用率
|
||||
private String memory; // 内存
|
||||
private String disk; // 磁盘
|
||||
private String usage; // 使用率
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.jeesite.modules.apps.web.docker;
|
||||
|
||||
import com.jeesite.modules.apps.Module.ContainerInfo;
|
||||
import com.jeesite.modules.apps.Module.DockerResult;
|
||||
import com.jeesite.modules.apps.Module.SystemInfo;
|
||||
import com.jeesite.modules.biz.dao.MySftpAccountsDao;
|
||||
import com.jeesite.modules.biz.entity.MySftpAccounts;
|
||||
import com.jeesite.modules.utils.DockerUtil;
|
||||
@@ -90,4 +91,16 @@ public class myContainerController {
|
||||
MySftpAccounts accounts = mySftpAccountsDao.get(mySftpAccounts);
|
||||
return DockerUtil.listContainers(accounts, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取CPU列表
|
||||
*/
|
||||
@RequestMapping(value = "systemInfo")
|
||||
@ResponseBody
|
||||
public SystemInfo systemInfo(ContainerInfo containerInfo){
|
||||
MySftpAccounts mySftpAccounts = new MySftpAccounts();
|
||||
mySftpAccounts.setAccountId(containerInfo.getAccountId());
|
||||
MySftpAccounts accounts = mySftpAccountsDao.get(mySftpAccounts);
|
||||
return DockerUtil.systemInfo(accounts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.jeesite.modules.apps.web.docker;
|
||||
|
||||
import com.jeesite.modules.apps.Module.ServerInfo;
|
||||
import com.jeesite.modules.apps.Module.SystemInfo;
|
||||
import com.jeesite.modules.biz.dao.MySftpAccountsDao;
|
||||
import com.jeesite.modules.biz.entity.MySftpAccounts;
|
||||
import com.jeesite.modules.utils.DockerUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.jeesite.modules.utils;
|
||||
import com.jcraft.jsch.*;
|
||||
import com.jeesite.modules.apps.Module.ContainerInfo;
|
||||
import com.jeesite.modules.apps.Module.DockerResult;
|
||||
import com.jeesite.modules.apps.Module.SystemInfo;
|
||||
import com.jeesite.modules.biz.entity.MySftpAccounts;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -14,7 +15,7 @@ import java.util.List;
|
||||
|
||||
public class DockerUtil {
|
||||
|
||||
private static final int SSH_TIMEOUT = 30000;
|
||||
private static final int SSH_TIMEOUT = 3000;
|
||||
|
||||
private static String runCommand(MySftpAccounts account, String cmd) {
|
||||
JSch jsch = new JSch();
|
||||
@@ -89,6 +90,7 @@ public class DockerUtil {
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static DockerResult start(MySftpAccounts accounts, String containerId) {
|
||||
String res = runCommand(accounts, "docker start " + containerId);
|
||||
return res != null ? DockerResult.ok(res) : DockerResult.fail("执行失败");
|
||||
@@ -120,36 +122,30 @@ public class DockerUtil {
|
||||
return res != null ? DockerResult.ok(res) : DockerResult.fail("查询详情失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取CPU使用率
|
||||
*/
|
||||
// 获取 CPU 使用率
|
||||
public static String getCpuUsage(MySftpAccounts accounts) {
|
||||
try {
|
||||
return runCommand(accounts, "top -bn1 | grep Cpu | awk '{print 100 - $8}'");
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
// 1秒采样,输出纯数字百分比
|
||||
return runCommand(accounts,
|
||||
"top -bn1 | grep 'Cpu(s)' | sed -n '1p' | awk '{printf \"%.1f\", 100 - $8}'");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取内存使用情况
|
||||
*/
|
||||
// 获取内存使用率
|
||||
public static String getMemoryUsage(MySftpAccounts accounts) {
|
||||
try {
|
||||
return runCommand(accounts, "free -m | grep Mem | awk '{print $3\"MB / \"$2\"MB\"}'");
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
return runCommand(accounts,
|
||||
"free | grep Mem | awk '{printf \"%.1f\", $3/$2*100}'");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取磁盘使用情况
|
||||
*/
|
||||
// 获取磁盘使用率
|
||||
public static String getDiskUsage(MySftpAccounts accounts) {
|
||||
try {
|
||||
return runCommand(accounts, "df -h / | grep / | awk '{print $3\" / \"$2}\" \"$5}'");
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
return runCommand(accounts,
|
||||
"df -h / | grep / | awk '{gsub(/%/,\"\"); print $5}'");
|
||||
}
|
||||
|
||||
public static SystemInfo systemInfo(MySftpAccounts accounts) {
|
||||
SystemInfo systemInfo = new SystemInfo();
|
||||
systemInfo.setCpu(getCpuUsage(accounts));
|
||||
systemInfo.setMemory(getMemoryUsage(accounts));
|
||||
systemInfo.setDisk(getDiskUsage(accounts));
|
||||
return systemInfo;
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,13 @@ export interface ServerInfo extends BasicModel<ServerInfo> {
|
||||
containerId: string; // 容器ID
|
||||
}
|
||||
|
||||
export interface SystemInfo extends BasicModel<SystemInfo> {
|
||||
cpu: string; // CPU使用率
|
||||
memory: string; // 内存
|
||||
disk: string; // 磁盘
|
||||
}
|
||||
|
||||
|
||||
export interface ContainerInfo extends BasicModel<ContainerInfo> {
|
||||
image: string; // 镜像名称
|
||||
command: string; // 启动命令
|
||||
@@ -42,6 +49,9 @@ defHttp.get<ServerInfo[]>({ url: adminPath + '/docker/myServer/listAll' });
|
||||
export const myContainerInfo = (params?: ContainerInfo | any) =>
|
||||
defHttp.get<ContainerInfo[]>({ url: adminPath + '/docker/myContainer/listAll', params });
|
||||
|
||||
export const mySystemInfo = (params?: ContainerInfo | any) =>
|
||||
defHttp.get<SystemInfo>({ url: adminPath + '/docker/myContainer/systemInfo', params });
|
||||
|
||||
export const myDockerStop = (params?: ContainerInfo | any) =>
|
||||
defHttp.get<DockerResult>({ url: adminPath + '/docker/myContainer/stop', params });
|
||||
|
||||
|
||||
@@ -18,15 +18,11 @@
|
||||
>
|
||||
<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>
|
||||
@@ -41,55 +37,60 @@
|
||||
</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>
|
||||
<transition name="detail-fade" mode="out-in">
|
||||
<div v-if="detailLoading" key="detail-skeleton" class="detail-skeleton">
|
||||
<div class="hero-card hero-card--skeleton">
|
||||
<div class="hero-card__skeleton-title"></div>
|
||||
<div class="hero-card__skeleton-meta"></div>
|
||||
<div class="hero-card__skeleton-meta hero-card__skeleton-meta--short"></div>
|
||||
</div>
|
||||
<div class="metric-grid">
|
||||
<div v-for="item in 3" :key="item" class="metric-card metric-card--skeleton">
|
||||
<div class="metric-card__label"> </div>
|
||||
<div class="metric-card__skeleton-value"></div>
|
||||
<div class="metric-card__skeleton-bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-card__right">
|
||||
<div class="hero-card__badge">{{ getServerStatus(currentServer).label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else key="detail-content">
|
||||
<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', { 'hero-card__badge--offline': !systemOnline }]">
|
||||
{{ systemOnline ? '在线' : '离线' }}
|
||||
</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 class="metric-grid">
|
||||
<div class="metric-card metric-card--cpu">
|
||||
<div class="metric-card__label">CPU</div>
|
||||
<div class="metric-card__value">{{ formatPercentText(systemInfo?.cpu) }}</div>
|
||||
<div class="metric-card__progress">
|
||||
<span :style="{ width: normalizePercent(systemInfo?.cpu) }"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-card metric-card--memory">
|
||||
<div class="metric-card__label">内存</div>
|
||||
<div class="metric-card__value">{{ formatPercentText(systemInfo?.memory) }}</div>
|
||||
<div class="metric-card__progress">
|
||||
<span :style="{ width: normalizePercent(systemInfo?.memory) }"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-card metric-card--disk">
|
||||
<div class="metric-card__label">磁盘</div>
|
||||
<div class="metric-card__value">{{ formatPercentText(systemInfo?.disk) }}</div>
|
||||
<div class="metric-card__progress">
|
||||
<span :style="{ width: normalizePercent(systemInfo?.disk) }"></span>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
</transition>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -228,6 +229,7 @@
|
||||
ContainerInfo,
|
||||
DockerResult,
|
||||
ServerInfo,
|
||||
SystemInfo,
|
||||
myContainerInfo,
|
||||
myDockerInspect,
|
||||
myDockerLogs,
|
||||
@@ -235,12 +237,16 @@
|
||||
myDockerStart,
|
||||
myDockerStop,
|
||||
myServerInfo,
|
||||
mySystemInfo,
|
||||
} from '@jeesite/biz/api/biz/myDocker';
|
||||
|
||||
const loading = ref(false);
|
||||
const metricLoading = ref(false);
|
||||
const detailLoading = ref(false);
|
||||
const serverOptions = ref<ServerInfo[]>([]);
|
||||
const sourceData = ref<ContainerInfo[]>([]);
|
||||
const selectedAccountId = ref('');
|
||||
const systemInfo = ref<SystemInfo | null>(null);
|
||||
const resultVisible = ref(false);
|
||||
const resultTitle = ref('');
|
||||
const resultContent = ref('');
|
||||
@@ -259,25 +265,22 @@
|
||||
() => serverOptions.value.find((item) => item.accountId === selectedAccountId.value) || null,
|
||||
);
|
||||
const currentContainers = computed(() => sourceData.value);
|
||||
const systemOnline = computed(() =>
|
||||
Boolean(systemInfo.value && (systemInfo.value.cpu || systemInfo.value.memory || systemInfo.value.disk)),
|
||||
);
|
||||
const runningCount = computed(
|
||||
() => sourceData.value.filter((item) => getStatusTagType(item.status) === 'success').length,
|
||||
);
|
||||
|
||||
function handleServerSelect(item: ServerInfo) {
|
||||
const accountId = item.accountId || '';
|
||||
detailLoading.value = true;
|
||||
selectedAccountId.value = accountId;
|
||||
containerQueryParams.accountId = accountId;
|
||||
getSystemInfo(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';
|
||||
@@ -286,6 +289,22 @@
|
||||
return 'info';
|
||||
}
|
||||
|
||||
function normalizePercent(value?: string) {
|
||||
const numeric = Number(
|
||||
String(value || '')
|
||||
.replace('%', '')
|
||||
.trim(),
|
||||
);
|
||||
if (Number.isNaN(numeric)) return '0%';
|
||||
return `${Math.max(0, Math.min(100, numeric))}%`;
|
||||
}
|
||||
|
||||
function formatPercentText(value?: string) {
|
||||
const text = String(value || '').trim();
|
||||
if (!text) return '0';
|
||||
return text.includes('%') ? text : `${text}%`;
|
||||
}
|
||||
|
||||
function isContainerStopped(status?: string) {
|
||||
const value = String(status || '').toLowerCase();
|
||||
return value.includes('exit') || value.includes('stop') || value.includes('dead');
|
||||
@@ -297,7 +316,9 @@
|
||||
if (!selectedAccountId.value && serverOptions.value.length) {
|
||||
selectedAccountId.value = serverOptions.value[0].accountId || '';
|
||||
if (selectedAccountId.value) {
|
||||
detailLoading.value = true;
|
||||
containerQueryParams.accountId = selectedAccountId.value;
|
||||
getSystemInfo(selectedAccountId.value);
|
||||
getContainerList();
|
||||
}
|
||||
}
|
||||
@@ -306,6 +327,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function getSystemInfo(accountId = containerQueryParams.accountId) {
|
||||
if (!accountId) {
|
||||
return;
|
||||
}
|
||||
metricLoading.value = true;
|
||||
detailLoading.value = true;
|
||||
try {
|
||||
const params = {
|
||||
accountId,
|
||||
};
|
||||
const res = await mySystemInfo(params);
|
||||
if (accountId === selectedAccountId.value) {
|
||||
systemInfo.value = res;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
if (accountId === selectedAccountId.value) {
|
||||
metricLoading.value = false;
|
||||
detailLoading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getContainerList() {
|
||||
if (!containerQueryParams.accountId) {
|
||||
sourceData.value = [];
|
||||
@@ -313,7 +358,10 @@
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
sourceData.value = (await myContainerInfo(containerQueryParams)) || [];
|
||||
const params = {
|
||||
accountId: containerQueryParams.accountId,
|
||||
};
|
||||
sourceData.value = (await myContainerInfo(params)) || [];
|
||||
} catch (error) {
|
||||
sourceData.value = [];
|
||||
} finally {
|
||||
@@ -503,7 +551,7 @@
|
||||
|
||||
.docker-main {
|
||||
display: grid;
|
||||
grid-template-rows: 360px minmax(0, 1fr);
|
||||
grid-template-rows: 270px minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
@@ -556,11 +604,16 @@
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
border-radius: 999px;
|
||||
background: rgb(30 64 175);
|
||||
color: rgb(239 246 255);
|
||||
background: rgb(220 252 231);
|
||||
color: rgb(21 128 61);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
|
||||
&--offline {
|
||||
background: rgb(254 226 226);
|
||||
color: rgb(185 28 28);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,6 +644,29 @@
|
||||
color: rgb(30 41 59);
|
||||
}
|
||||
|
||||
&__skeleton {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
&__skeleton-value,
|
||||
&__skeleton-bar {
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(90deg, rgb(226 232 240) 25%, rgb(241 245 249) 50%, rgb(226 232 240) 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: metric-skeleton 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
&__skeleton-value {
|
||||
width: 56px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
&__skeleton-bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
&__progress {
|
||||
height: 8px;
|
||||
margin-top: 12px;
|
||||
@@ -625,6 +701,25 @@
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.metric-fade-enter-active,
|
||||
.metric-fade-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.metric-fade-enter-from,
|
||||
.metric-fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@keyframes metric-skeleton {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
padding: 12px;
|
||||
border-radius: 10px;
|
||||
@@ -643,7 +738,82 @@
|
||||
line-height: 20px;
|
||||
color: rgb(30 41 59);
|
||||
word-break: break-all;
|
||||
|
||||
&--status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&__dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 999px;
|
||||
background: rgb(34 197 94);
|
||||
|
||||
&--offline {
|
||||
background: rgb(239 68 68);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.detail-skeleton {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.hero-card--skeleton {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.hero-card__skeleton-title,
|
||||
.hero-card__skeleton-meta,
|
||||
.detail-card__skeleton-label,
|
||||
.detail-card__skeleton-value {
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(90deg, rgb(226 232 240) 25%, rgb(241 245 249) 50%, rgb(226 232 240) 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: metric-skeleton 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.hero-card__skeleton-title {
|
||||
width: 160px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.hero-card__skeleton-meta {
|
||||
width: 220px;
|
||||
height: 14px;
|
||||
|
||||
&--short {
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-card__skeleton-label {
|
||||
width: 80px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.detail-card__skeleton-value {
|
||||
width: 70%;
|
||||
height: 18px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.detail-fade-enter-active,
|
||||
.detail-fade-leave-active {
|
||||
transition: opacity 0.25s ease;
|
||||
}
|
||||
|
||||
.detail-fade-enter-from,
|
||||
.detail-fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.docker-table {
|
||||
@@ -875,8 +1045,13 @@
|
||||
}
|
||||
|
||||
&__badge {
|
||||
background: rgb(30 64 175 / 80%);
|
||||
color: rgb(219 234 254);
|
||||
background: rgb(20 83 45 / 72%);
|
||||
color: rgb(187 247 208);
|
||||
|
||||
&--offline {
|
||||
background: rgb(127 29 29 / 72%);
|
||||
color: rgb(254 202 202);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -895,6 +1070,12 @@
|
||||
color: rgb(226 232 240);
|
||||
}
|
||||
|
||||
&__skeleton-value,
|
||||
&__skeleton-bar {
|
||||
background: linear-gradient(90deg, rgb(51 65 85) 25%, rgb(71 85 105) 50%, rgb(51 65 85) 75%);
|
||||
background-size: 200% 100%;
|
||||
}
|
||||
|
||||
&__progress {
|
||||
background: rgb(51 65 85);
|
||||
}
|
||||
@@ -910,6 +1091,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.hero-card__skeleton-title,
|
||||
.hero-card__skeleton-meta,
|
||||
.detail-card__skeleton-label,
|
||||
.detail-card__skeleton-value {
|
||||
background: linear-gradient(90deg, rgb(51 65 85) 25%, rgb(71 85 105) 50%, rgb(51 65 85) 75%);
|
||||
background-size: 200% 100%;
|
||||
}
|
||||
|
||||
.docker-table {
|
||||
.el-table {
|
||||
--el-table-header-bg-color: rgb(20, 20, 20);
|
||||
@@ -1041,7 +1230,7 @@
|
||||
}
|
||||
|
||||
.docker-main {
|
||||
grid-template-rows: 400px minmax(0, 1fr);
|
||||
grid-template-rows: 300px minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user