mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-12-19 22:09:03 +08:00
feat: add Docker Inspect dialog with tabbed container info view and enhanced visual formatting (#10783)
* feat: Expand Docker inspect detail drawer * chore: i18n * fix: switch order in mount detail card.
This commit is contained in:
parent
db002ec915
commit
ea878bba73
12 changed files with 349 additions and 10 deletions
|
|
@ -1032,6 +1032,7 @@ const message = {
|
||||||
author: 'Author',
|
author: 'Author',
|
||||||
ifPause: 'Pause Container During Creation',
|
ifPause: 'Pause Container During Creation',
|
||||||
ifMakeImageWithContainer: 'Create New Image from This Container?',
|
ifMakeImageWithContainer: 'Create New Image from This Container?',
|
||||||
|
finishTime: 'Last stop time',
|
||||||
},
|
},
|
||||||
cronjob: {
|
cronjob: {
|
||||||
create: 'Create cron job',
|
create: 'Create cron job',
|
||||||
|
|
|
||||||
|
|
@ -1031,6 +1031,7 @@ const message = {
|
||||||
author: 'Autor',
|
author: 'Autor',
|
||||||
ifPause: '¿Pausar el contenedor durante la creación?',
|
ifPause: '¿Pausar el contenedor durante la creación?',
|
||||||
ifMakeImageWithContainer: '¿Crear nueva imagen a partir de este contenedor?',
|
ifMakeImageWithContainer: '¿Crear nueva imagen a partir de este contenedor?',
|
||||||
|
finishTime: 'Hora de la última detención',
|
||||||
},
|
},
|
||||||
cronjob: {
|
cronjob: {
|
||||||
create: 'Crear tarea programada',
|
create: 'Crear tarea programada',
|
||||||
|
|
|
||||||
|
|
@ -1003,6 +1003,7 @@ const message = {
|
||||||
author: '著者',
|
author: '著者',
|
||||||
ifPause: '作成中にコンテナを一時停止します',
|
ifPause: '作成中にコンテナを一時停止します',
|
||||||
ifMakeImageWithContainer: 'このコンテナから新しい画像を作成しますか?',
|
ifMakeImageWithContainer: 'このコンテナから新しい画像を作成しますか?',
|
||||||
|
finishTime: '前回の停止時間',
|
||||||
},
|
},
|
||||||
cronjob: {
|
cronjob: {
|
||||||
create: 'Cronジョブを作成します',
|
create: 'Cronジョブを作成します',
|
||||||
|
|
|
||||||
|
|
@ -993,6 +993,7 @@ const message = {
|
||||||
author: '작성자',
|
author: '작성자',
|
||||||
ifPause: '생성 중 컨테이너 일시 정지',
|
ifPause: '생성 중 컨테이너 일시 정지',
|
||||||
ifMakeImageWithContainer: '이 컨테이너에서 새 이미지를 생성하시겠습니까?',
|
ifMakeImageWithContainer: '이 컨테이너에서 새 이미지를 생성하시겠습니까?',
|
||||||
|
finishTime: '마지막 중지 시간',
|
||||||
},
|
},
|
||||||
cronjob: {
|
cronjob: {
|
||||||
create: '크론 작업 생성',
|
create: '크론 작업 생성',
|
||||||
|
|
|
||||||
|
|
@ -1027,6 +1027,7 @@ const message = {
|
||||||
author: 'Pengarang',
|
author: 'Pengarang',
|
||||||
ifPause: 'Jeda Kontena Semasa Penciptaan',
|
ifPause: 'Jeda Kontena Semasa Penciptaan',
|
||||||
ifMakeImageWithContainer: 'Cipta Imej Baru daripada Kontena Ini?',
|
ifMakeImageWithContainer: 'Cipta Imej Baru daripada Kontena Ini?',
|
||||||
|
finishTime: 'Masa berhenti terakhir',
|
||||||
},
|
},
|
||||||
cronjob: {
|
cronjob: {
|
||||||
create: 'Cipta tugas cron',
|
create: 'Cipta tugas cron',
|
||||||
|
|
|
||||||
|
|
@ -1024,6 +1024,7 @@ const message = {
|
||||||
author: 'Autor',
|
author: 'Autor',
|
||||||
ifPause: 'Pausar container durante a criação',
|
ifPause: 'Pausar container durante a criação',
|
||||||
ifMakeImageWithContainer: 'Criar nova imagem a partir deste container?',
|
ifMakeImageWithContainer: 'Criar nova imagem a partir deste container?',
|
||||||
|
finishTime: 'Horário da última parada',
|
||||||
},
|
},
|
||||||
cronjob: {
|
cronjob: {
|
||||||
create: 'Criar tarefa cron',
|
create: 'Criar tarefa cron',
|
||||||
|
|
|
||||||
|
|
@ -1022,6 +1022,7 @@ const message = {
|
||||||
author: 'Автор',
|
author: 'Автор',
|
||||||
ifPause: 'Приостановить контейнер во время создания',
|
ifPause: 'Приостановить контейнер во время создания',
|
||||||
ifMakeImageWithContainer: 'Создать новый образ из этого контейнера?',
|
ifMakeImageWithContainer: 'Создать новый образ из этого контейнера?',
|
||||||
|
finishTime: 'Время последней остановки',
|
||||||
},
|
},
|
||||||
cronjob: {
|
cronjob: {
|
||||||
create: 'Создать задачу cron',
|
create: 'Создать задачу cron',
|
||||||
|
|
|
||||||
|
|
@ -1044,6 +1044,7 @@ const message = {
|
||||||
author: 'Yazar',
|
author: 'Yazar',
|
||||||
ifPause: 'Oluşturma Sırasında Konteyneri Duraklat',
|
ifPause: 'Oluşturma Sırasında Konteyneri Duraklat',
|
||||||
ifMakeImageWithContainer: 'Bu Konteynerden Yeni İmaj Oluşturulsun mu?',
|
ifMakeImageWithContainer: 'Bu Konteynerden Yeni İmaj Oluşturulsun mu?',
|
||||||
|
finishTime: 'Son durdurma zamanı',
|
||||||
},
|
},
|
||||||
cronjob: {
|
cronjob: {
|
||||||
create: 'Cron görevi oluştur',
|
create: 'Cron görevi oluştur',
|
||||||
|
|
|
||||||
|
|
@ -980,6 +980,7 @@ const message = {
|
||||||
author: '作者',
|
author: '作者',
|
||||||
ifPause: '製作過程中是否暫停容器',
|
ifPause: '製作過程中是否暫停容器',
|
||||||
ifMakeImageWithContainer: '是否根據此容器製作新鏡像?',
|
ifMakeImageWithContainer: '是否根據此容器製作新鏡像?',
|
||||||
|
finishTime: '上一次停止時間',
|
||||||
},
|
},
|
||||||
cronjob: {
|
cronjob: {
|
||||||
create: '建立計劃任務',
|
create: '建立計劃任務',
|
||||||
|
|
|
||||||
|
|
@ -980,6 +980,7 @@ const message = {
|
||||||
author: '作者',
|
author: '作者',
|
||||||
ifPause: '制作过程中是否暂停容器',
|
ifPause: '制作过程中是否暂停容器',
|
||||||
ifMakeImageWithContainer: '是否根据此容器制作新镜像?',
|
ifMakeImageWithContainer: '是否根据此容器制作新镜像?',
|
||||||
|
finishTime: '上一次停止时间',
|
||||||
},
|
},
|
||||||
cronjob: {
|
cronjob: {
|
||||||
create: '创建计划任务',
|
create: '创建计划任务',
|
||||||
|
|
|
||||||
|
|
@ -337,7 +337,7 @@
|
||||||
|
|
||||||
<OpDialog ref="opRef" @search="search" />
|
<OpDialog ref="opRef" @search="search" />
|
||||||
|
|
||||||
<CodemirrorDrawer ref="myDetail" />
|
<ContainerInspectDialog ref="containerInspectRef" />
|
||||||
<PruneDialog @search="search" ref="dialogPruneRef" />
|
<PruneDialog @search="search" ref="dialogPruneRef" />
|
||||||
|
|
||||||
<RenameDialog @search="search" ref="dialogRenameRef" />
|
<RenameDialog @search="search" ref="dialogRenameRef" />
|
||||||
|
|
@ -358,7 +358,7 @@ import UpgradeDialog from '@/views/container/container/upgrade/index.vue';
|
||||||
import CommitDialog from '@/views/container/container/commit/index.vue';
|
import CommitDialog from '@/views/container/container/commit/index.vue';
|
||||||
import MonitorDialog from '@/views/container/container/monitor/index.vue';
|
import MonitorDialog from '@/views/container/container/monitor/index.vue';
|
||||||
import TerminalDialog from '@/views/container/container/terminal/index.vue';
|
import TerminalDialog from '@/views/container/container/terminal/index.vue';
|
||||||
import CodemirrorDrawer from '@/components/codemirror-pro/drawer.vue';
|
import ContainerInspectDialog from '@/views/container/container/inspect/index.vue';
|
||||||
import PortJumpDialog from '@/components/port-jump/index.vue';
|
import PortJumpDialog from '@/components/port-jump/index.vue';
|
||||||
import DockerStatus from '@/views/container/docker-status/index.vue';
|
import DockerStatus from '@/views/container/docker-status/index.vue';
|
||||||
import ContainerLogDialog from '@/components/log/container-drawer/index.vue';
|
import ContainerLogDialog from '@/components/log/container-drawer/index.vue';
|
||||||
|
|
@ -431,7 +431,7 @@ const props = withDefaults(defineProps<Filters>(), {
|
||||||
filters: '',
|
filters: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const myDetail = ref();
|
const containerInspectRef = ref();
|
||||||
|
|
||||||
const dialogContainerLogRef = ref();
|
const dialogContainerLogRef = ref();
|
||||||
const dialogRenameRef = ref();
|
const dialogRenameRef = ref();
|
||||||
|
|
@ -611,13 +611,7 @@ const onTerminal = (row: any) => {
|
||||||
|
|
||||||
const onInspect = async (id: string) => {
|
const onInspect = async (id: string) => {
|
||||||
const res = await inspect({ id: id, type: 'container' });
|
const res = await inspect({ id: id, type: 'container' });
|
||||||
let detailInfo = JSON.stringify(JSON.parse(res.data), null, 2);
|
containerInspectRef.value!.acceptParams({ data: res.data });
|
||||||
let param = {
|
|
||||||
header: i18n.global.t('commons.button.view'),
|
|
||||||
detailInfo: detailInfo,
|
|
||||||
mode: 'json',
|
|
||||||
};
|
|
||||||
myDetail.value!.acceptParams(param);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClean = () => {
|
const onClean = () => {
|
||||||
|
|
|
||||||
335
frontend/src/views/container/container/inspect/index.vue
Normal file
335
frontend/src/views/container/container/inspect/index.vue
Normal file
|
|
@ -0,0 +1,335 @@
|
||||||
|
<template>
|
||||||
|
<DrawerPro v-model="visible" :header="$t('commons.button.view')" size="large" @close="handleClose">
|
||||||
|
<el-tabs v-model="activeTab" type="border-card">
|
||||||
|
<!-- 概览 Overview -->
|
||||||
|
<el-tab-pane :label="$t('menu.container')" name="overview">
|
||||||
|
<el-card shadow="never" class="mb-2.5">
|
||||||
|
<el-descriptions :column="1" border>
|
||||||
|
<el-descriptions-item :label="$t('commons.table.name')">
|
||||||
|
<el-text>{{ inspectData?.Name?.substring(1) || '-' }}</el-text>
|
||||||
|
<CopyButton :content="inspectData?.Name || ''" />
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="ID">
|
||||||
|
<el-text class="text-xs">{{ inspectData?.Id?.substring(0, 12) }}</el-text>
|
||||||
|
<CopyButton :content="inspectData?.Id || ''" />
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="PID">
|
||||||
|
{{ inspectData?.State?.Pid }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :label="$t('container.image')">
|
||||||
|
{{ inspectData?.Config?.Image }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :label="$t('commons.table.createdAt')">
|
||||||
|
{{ formatDate(inspectData?.Created) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :label="$t('process.startTime')">
|
||||||
|
{{ formatDate(inspectData?.State?.StartedAt) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :label="$t('container.finishTime')">
|
||||||
|
{{ formatDate(inspectData?.State?.FinishedAt) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :label="$t('container.restartPolicy')">
|
||||||
|
<el-tag>{{ inspectData?.HostConfig?.RestartPolicy?.Name }}</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :label="$t('commons.table.status')">
|
||||||
|
<el-tag :type="getStatusType(inspectData?.State?.Status)">
|
||||||
|
{{ inspectData?.State?.Status }}
|
||||||
|
{{
|
||||||
|
inspectData?.State?.Health?.Status
|
||||||
|
? '(' + inspectData?.State?.Health?.Status + ')'
|
||||||
|
: ''
|
||||||
|
}}
|
||||||
|
</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</el-card>
|
||||||
|
<el-card shadow="never" class="mb-2.5">
|
||||||
|
<template #header>
|
||||||
|
<span class="font-semibold">{{ $t('container.command') }}</span>
|
||||||
|
</template>
|
||||||
|
<el-descriptions :column="1" border>
|
||||||
|
<el-descriptions-item :label="$t('container.command')">
|
||||||
|
<div v-if="inspectData?.Config?.Cmd?.length">
|
||||||
|
{{ inspectData?.Config?.Cmd.join(' ') }}
|
||||||
|
</div>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="ENTRYPONT">
|
||||||
|
<div v-if="inspectData?.Config?.Entrypoint?.length">
|
||||||
|
<el-tag
|
||||||
|
v-for="(entry, index) in inspectData?.Config?.Entrypoint"
|
||||||
|
:key="index"
|
||||||
|
class="mr-1 mb-1"
|
||||||
|
>
|
||||||
|
{{ entry }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :label="$t('container.workingDir')">
|
||||||
|
{{ inspectData?.Config?.WorkingDir || '-' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card shadow="never" class="mb-2.5">
|
||||||
|
<template #header>
|
||||||
|
<span class="font-semibold">{{ $t('container.env') }}</span>
|
||||||
|
</template>
|
||||||
|
<el-collapse accordion>
|
||||||
|
<el-collapse-item v-for="(env, index) in inspectData?.Config?.Env" :key="index" :name="index">
|
||||||
|
<template #title>
|
||||||
|
<el-text class="text-sm">{{ getEnvKey(env) }}</el-text>
|
||||||
|
</template>
|
||||||
|
<el-text class="text-xs break-all">{{ getEnvValue(env) }}</el-text>
|
||||||
|
<CopyButton :content="getEnvValue(env)" />
|
||||||
|
</el-collapse-item>
|
||||||
|
</el-collapse>
|
||||||
|
<el-empty v-if="!inspectData?.Config?.Env?.length" :description="$t('commons.msg.noData')" />
|
||||||
|
</el-card>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<!-- 网络 Network -->
|
||||||
|
<el-tab-pane :label="$t('container.network')" name="network">
|
||||||
|
<el-card shadow="never" class="mb-2.5">
|
||||||
|
<template #header>
|
||||||
|
<span class="font-semibold">{{ $t('container.network') }}</span>
|
||||||
|
</template>
|
||||||
|
<el-descriptions :column="1" border>
|
||||||
|
<el-descriptions-item :label="$t('container.networkName')">
|
||||||
|
<el-tag>{{ inspectData?.HostConfig?.NetworkMode }}</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :label="$t('home.hostname')">
|
||||||
|
{{ inspectData?.Config?.Hostname }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="Domain">
|
||||||
|
{{ inspectData?.Config?.Domainname || '-' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
<el-table :data="getPortBindings()" border class="mt-2">
|
||||||
|
<el-table-column :label="$t('commons.table.port')">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span>{{ row.hostIp }}:{{ row.hostPort }}</span>
|
||||||
|
<span class="mx-2">→</span>
|
||||||
|
<span>{{ row.containerPort }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card shadow="never" class="mb-2.5">
|
||||||
|
<div v-for="(network, name) in inspectData?.NetworkSettings?.Networks" :key="name" class="mb-4">
|
||||||
|
<div class="text-base font-semibold mb-2">{{ name }}</div>
|
||||||
|
<el-descriptions :column="2" border>
|
||||||
|
<el-descriptions-item label="Network ID">
|
||||||
|
<el-text class="text-xs">{{ network?.NetworkID?.substring(0, 12) }}</el-text>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="Endpoint ID">
|
||||||
|
<el-text class="text-xs">{{ network?.EndpointID?.substring(0, 12) }}</el-text>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="IPv4">
|
||||||
|
{{ network?.IPAddress || '-' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="IPv4 Gateway">
|
||||||
|
{{ network?.Gateway || '-' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="MAC">
|
||||||
|
{{ network?.MacAddress || '-' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="IPv6 Gateway">
|
||||||
|
{{ network?.IPv6Gateway || '-' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<!-- 存储 Storage -->
|
||||||
|
<el-tab-pane :label="$t('container.volume')" name="storage">
|
||||||
|
<el-card shadow="never" class="mb-2.5">
|
||||||
|
<template #header>
|
||||||
|
<span class="font-semibold">{{ $t('container.mountpoint') }}</span>
|
||||||
|
</template>
|
||||||
|
<el-table :data="inspectData?.Mounts" border>
|
||||||
|
<el-table-column :label="$t('commons.table.type')" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag size="small">{{ row.Type }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('container.hostOption')" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-text
|
||||||
|
v-if="row.Source"
|
||||||
|
type="primary"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="handleJumpToFile(row.Source)"
|
||||||
|
>
|
||||||
|
{{ row.Source }}
|
||||||
|
</el-text>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
:label="$t('container.volumeOption')"
|
||||||
|
prop="Destination"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column :label="$t('container.tag')" width="80" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.RW ? 'success' : 'warning'" size="small">
|
||||||
|
{{ row.RW ? $t('container.modeRW') : $t('container.modeR') }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<el-tab-pane :label="$t('commons.button.view')" name="view">
|
||||||
|
<el-card shadow="never" class="mb-2.5">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="font-semibold">{{ $t('commons.button.view') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div>
|
||||||
|
<CodemirrorPro v-model="rawJson" :height-diff="200" :disabled="true" mode="json" />
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="visible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</DrawerPro>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
|
||||||
|
import { routerToFileWithPath } from '@/utils/router';
|
||||||
|
|
||||||
|
const visible = ref(false);
|
||||||
|
const activeTab = ref('overview');
|
||||||
|
const inspectData = ref<any>(null);
|
||||||
|
const rawJson = ref('');
|
||||||
|
const showRawJson = ref(false);
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
data: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const acceptParams = (props: DialogProps): void => {
|
||||||
|
visible.value = true;
|
||||||
|
activeTab.value = 'overview';
|
||||||
|
showRawJson.value = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof props.data === 'string') {
|
||||||
|
inspectData.value = JSON.parse(props.data);
|
||||||
|
} else {
|
||||||
|
inspectData.value = props.data;
|
||||||
|
}
|
||||||
|
rawJson.value = JSON.stringify(inspectData.value, null, 2);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse inspect data:', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
visible.value = false;
|
||||||
|
inspectData.value = null;
|
||||||
|
rawJson.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusType = (status: string): string => {
|
||||||
|
const statusMap: Record<string, string> = {
|
||||||
|
running: 'success',
|
||||||
|
paused: 'warning',
|
||||||
|
restarting: 'warning',
|
||||||
|
exited: 'info',
|
||||||
|
dead: 'danger',
|
||||||
|
};
|
||||||
|
return statusMap[status?.toLowerCase()] || 'info';
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDate = (dateStr: string): string => {
|
||||||
|
if (!dateStr || dateStr === '0001-01-01T00:00:00Z') {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
const y = date.getFullYear();
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const d = String(date.getDate()).padStart(2, '0');
|
||||||
|
const h = String(date.getHours()).padStart(2, '0');
|
||||||
|
const minute = String(date.getMinutes()).padStart(2, '0');
|
||||||
|
const second = String(date.getSeconds()).padStart(2, '0');
|
||||||
|
return `${y}-${m}-${d} ${h}:${minute}:${second}`;
|
||||||
|
} catch {
|
||||||
|
return dateStr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPortBindings = (): any[] => {
|
||||||
|
const ports: any[] = [];
|
||||||
|
const portBindings = inspectData.value?.HostConfig?.PortBindings || {};
|
||||||
|
|
||||||
|
for (const [containerPort, bindings] of Object.entries(portBindings)) {
|
||||||
|
if (Array.isArray(bindings)) {
|
||||||
|
for (const binding of bindings) {
|
||||||
|
ports.push({
|
||||||
|
containerPort,
|
||||||
|
hostIp: (binding as any).HostIp || '0.0.0.0',
|
||||||
|
hostPort: (binding as any).HostPort,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ports;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEnvKey = (env: string): string => {
|
||||||
|
const index = env.indexOf('=');
|
||||||
|
return index > 0 ? env.substring(0, index) : env;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEnvValue = (env: string): string => {
|
||||||
|
const index = env.indexOf('=');
|
||||||
|
return index > 0 ? env.substring(index + 1) : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleJumpToFile = (path: string) => {
|
||||||
|
if (path) {
|
||||||
|
routerToFileWithPath(path);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.break-all {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__header) {
|
||||||
|
padding: 12px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-descriptions__label) {
|
||||||
|
width: 180px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table) {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Add table
Reference in a new issue