Update DockerManager.vue
This commit is contained in:
@@ -283,7 +283,8 @@ onUnmounted(() => {
|
|||||||
<div v-if="containers.length === 0 && !isLoading" class="empty-placeholder">
|
<div v-if="containers.length === 0 && !isLoading" class="empty-placeholder">
|
||||||
{{ t('dockerManager.noContainers') }}
|
{{ t('dockerManager.noContainers') }}
|
||||||
</div>
|
</div>
|
||||||
<table v-else>
|
<!-- Add class="responsive-table" -->
|
||||||
|
<table v-else class="responsive-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ t('dockerManager.header.name') }}</th>
|
<th>{{ t('dockerManager.header.name') }}</th>
|
||||||
@@ -294,19 +295,17 @@ onUnmounted(() => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<!-- Key change: Use container.id -->
|
|
||||||
<tr v-for="container in containers" :key="container.id">
|
<tr v-for="container in containers" :key="container.id">
|
||||||
<td>{{ container.Names?.join(', ') || 'N/A' }}</td>
|
<!-- Add data-label attributes -->
|
||||||
<td>{{ container.Image }}</td>
|
<td :data-label="t('dockerManager.header.name')">{{ container.Names?.join(', ') || 'N/A' }}</td>
|
||||||
<td>
|
<td :data-label="t('dockerManager.header.image')">{{ container.Image }}</td>
|
||||||
|
<td :data-label="t('dockerManager.header.status')">
|
||||||
<span :class="['status-badge', `status-${container.State?.toLowerCase()}`]">
|
<span :class="['status-badge', `status-${container.State?.toLowerCase()}`]">
|
||||||
{{ container.Status }}
|
{{ container.Status }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<!-- Corrected Port mapping display logic -->
|
<td :data-label="t('dockerManager.header.ports')">{{ container.Ports?.map(p => `${p.IP ? p.IP + ':' : ''}${p.PublicPort ? p.PublicPort + '->' : ''}${p.PrivatePort}/${p.Type}`).join(', ') || 'N/A' }}</td>
|
||||||
<td>{{ container.Ports?.map(p => `${p.IP ? p.IP + ':' : ''}${p.PublicPort ? p.PublicPort + '->' : ''}${p.PrivatePort}/${p.Type}`).join(', ') || 'N/A' }}</td>
|
<td :data-label="t('dockerManager.header.actions')" class="action-buttons">
|
||||||
<td class="action-buttons">
|
|
||||||
<!-- Pass container.id instead of container.Id -->
|
|
||||||
<button @click="sendDockerCommand(container.id, 'start')" :title="t('dockerManager.action.start')" class="action-btn start" :disabled="container.State === 'running'">
|
<button @click="sendDockerCommand(container.id, 'start')" :title="t('dockerManager.action.start')" class="action-btn start" :disabled="container.State === 'running'">
|
||||||
<i class="fas fa-play"></i>
|
<i class="fas fa-play"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -319,6 +318,7 @@ onUnmounted(() => {
|
|||||||
<button @click="sendDockerCommand(container.id, 'remove')" :title="t('dockerManager.action.remove')" class="action-btn remove" :disabled="container.State === 'running'">
|
<button @click="sendDockerCommand(container.id, 'remove')" :title="t('dockerManager.action.remove')" class="action-btn remove" :disabled="container.State === 'running'">
|
||||||
<i class="fas fa-trash-alt"></i>
|
<i class="fas fa-trash-alt"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<!-- Log button removed as per user request -->
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -329,16 +329,22 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* Styles remain largely the same */
|
/* Styles remain largely the same */
|
||||||
|
/* --- Define the component root as a size container --- */
|
||||||
|
/* Remove padding from the main container */
|
||||||
.docker-manager {
|
.docker-manager {
|
||||||
padding: var(--base-padding, 1rem);
|
/* padding: var(--base-padding, 1rem); */ /* Removed */
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: auto;
|
/* overflow-y: auto; */ /* Let children handle scroll */
|
||||||
background-color: var(--app-bg-color);
|
background-color: var(--app-bg-color);
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
|
container-type: inline-size; /* Define as a size container */
|
||||||
|
container-name: docker-manager-pane; /* Optional: give it a name */
|
||||||
|
overflow: hidden; /* Prevent double scrollbars */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Add padding to placeholders */
|
||||||
.loading-placeholder,
|
.loading-placeholder,
|
||||||
.error-placeholder,
|
.error-placeholder,
|
||||||
.unavailable-placeholder,
|
.unavailable-placeholder,
|
||||||
@@ -351,6 +357,7 @@ onUnmounted(() => {
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
color: var(--text-color-secondary);
|
color: var(--text-color-secondary);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
padding: var(--base-padding, 1rem); /* Added padding */
|
||||||
}
|
}
|
||||||
.unavailable-placeholder i:first-child, .loading-placeholder i:first-child { /* Target the icon */
|
.unavailable-placeholder i:first-child, .loading-placeholder i:first-child { /* Target the icon */
|
||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
@@ -388,9 +395,11 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Add padding to the scrollable container and handle overflow */
|
||||||
.container-list {
|
.container-list {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
overflow-x: auto;
|
overflow: auto; /* Use auto for both x and y scroll */
|
||||||
|
padding: var(--base-padding, 1rem); /* Added padding */
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
@@ -464,4 +473,91 @@ tbody tr:hover {
|
|||||||
.action-btn.restart:not([disabled]):hover { color: var(--color-info, #17a2b8); }
|
.action-btn.restart:not([disabled]):hover { color: var(--color-info, #17a2b8); }
|
||||||
.action-btn.remove:not([disabled]):hover { color: var(--color-danger, #dc3545); }
|
.action-btn.remove:not([disabled]):hover { color: var(--color-danger, #dc3545); }
|
||||||
|
|
||||||
|
/* --- Responsive Table Styles using Container Query --- */
|
||||||
|
/* Target the container directly */
|
||||||
|
@container (max-width: 768px) {
|
||||||
|
.responsive-table {
|
||||||
|
border: none; /* Remove table border */
|
||||||
|
}
|
||||||
|
|
||||||
|
.responsive-table thead {
|
||||||
|
display: none; /* Hide table header */
|
||||||
|
}
|
||||||
|
|
||||||
|
.responsive-table tr {
|
||||||
|
display: block; /* Make rows behave like blocks/cards */
|
||||||
|
margin-bottom: 1rem; /* Space between cards */
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius-medium, 4px);
|
||||||
|
padding: 0.8rem;
|
||||||
|
background-color: var(--item-bg-color, var(--app-bg-color)); /* Card background */
|
||||||
|
box-shadow: var(--shadow-sm, 0 1px 2px 0 rgba(0, 0, 0, 0.05));
|
||||||
|
}
|
||||||
|
|
||||||
|
.responsive-table td {
|
||||||
|
display: block; /* Stack cells vertically */
|
||||||
|
text-align: right; /* Align cell content to the right */
|
||||||
|
padding-left: 50%; /* Make space for the label */
|
||||||
|
position: relative; /* Needed for pseudo-element positioning */
|
||||||
|
border-bottom: none; /* Remove default bottom border */
|
||||||
|
padding-top: 0.4rem;
|
||||||
|
padding-bottom: 0.4rem;
|
||||||
|
white-space: normal; /* Keep allowing wrapping */
|
||||||
|
word-break: break-all; /* Add this to force breaks in long strings */
|
||||||
|
}
|
||||||
|
.responsive-table td:not(:last-child) {
|
||||||
|
/* Optional: add a subtle separator between fields in a card */
|
||||||
|
border-bottom: 1px dashed var(--border-color-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.responsive-table td::before {
|
||||||
|
content: attr(data-label); /* Display the label */
|
||||||
|
position: absolute;
|
||||||
|
left: 0.8rem; /* Position label on the left */
|
||||||
|
width: calc(50% - 1.6rem); /* Calculate label width */
|
||||||
|
padding-right: 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: left; /* Align label text to the left */
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust specific cells if needed */
|
||||||
|
.responsive-table td:first-child { /* e.g., Name */
|
||||||
|
font-weight: 500; /* Make name slightly bolder */
|
||||||
|
}
|
||||||
|
|
||||||
|
.responsive-table .action-buttons {
|
||||||
|
text-align: right; /* Keep buttons aligned right */
|
||||||
|
padding-left: 0; /* Remove padding override for actions */
|
||||||
|
padding-top: 0.8rem; /* Add some space above buttons */
|
||||||
|
border-bottom: none; /* Ensure no border below buttons */
|
||||||
|
display: flex; /* Use flex for better button alignment */
|
||||||
|
justify-content: flex-end; /* Align buttons to the right */
|
||||||
|
flex-wrap: wrap; /* Allow buttons to wrap */
|
||||||
|
gap: 0.5rem; /* Keep gap between buttons */
|
||||||
|
}
|
||||||
|
.responsive-table .action-buttons::before {
|
||||||
|
display: none; /* Hide label for action buttons cell */
|
||||||
|
}
|
||||||
|
.responsive-table .action-buttons button {
|
||||||
|
/* Ensure buttons don't get too small */
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust status badge alignment and prevent wrapping */
|
||||||
|
.responsive-table td[data-label*="Status"] {
|
||||||
|
display: flex; /* Use flexbox for alignment */
|
||||||
|
justify-content: flex-end; /* Align badge to the right */
|
||||||
|
align-items: center; /* Vertically align if needed */
|
||||||
|
}
|
||||||
|
.responsive-table td[data-label*="Status"] span.status-badge {
|
||||||
|
/* float: right; */ /* Removed float */
|
||||||
|
white-space: nowrap; /* Prevent text inside badge from wrapping */
|
||||||
|
flex-shrink: 0; /* Prevent badge from shrinking */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* --- End Responsive Table Styles --- */
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
Reference in New Issue
Block a user