mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-25 12:52:25 +00:00
feat: improve UI consistency with refined styling
- Reduced text sizes across all tables for better information density - Guest names now use text-sm for better readability - Node names in summary tables use smaller text-[11px] - All table text sizes are now consistent - Unified table header styling with compromise design - Subtle bg-gray-50 dark:bg-gray-700/50 background - Consistent px-2 py-1.5 padding for comfortable spacing - text-[11px] sm:text-xs responsive sizing - Softer border-gray-200 dark:border-gray-600 borders - Updated Storage tab progress bars to match Dashboard style - Increased height from h-2.5 to h-3.5 - Simplified color thresholds (green/yellow/red) - Consistent text formatting with Dashboard Applied consistently across Dashboard, Storage, Backups tabs and all node tables for a cohesive look
This commit is contained in:
@@ -88,7 +88,7 @@ export function ResourceTable(props: ResourceTableProps) {
|
||||
<>
|
||||
{/* Node group header */}
|
||||
<tr class="bg-gray-50 dark:bg-gray-700/50">
|
||||
<td colspan={props.columns.length + 5} class="px-3 py-1 text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
<td colspan={props.columns.length + 5} class="p-1 px-2 text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
{nodeName}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -107,7 +107,7 @@ export function ResourceTable(props: ResourceTableProps) {
|
||||
|
||||
return (
|
||||
<tr class={`hover:bg-gray-50 dark:hover:bg-gray-900/50 transition-colors ${resource.disabled ? 'opacity-40' : ''}`}>
|
||||
<td class="px-3 py-1.5">
|
||||
<td class="p-1 px-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class={`text-sm font-medium ${resource.disabled ? 'text-gray-500 dark:text-gray-500' : 'text-gray-900 dark:text-gray-100'}`}>
|
||||
{resource.name}
|
||||
@@ -122,7 +122,7 @@ export function ResourceTable(props: ResourceTableProps) {
|
||||
</Show>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-3 py-1.5">
|
||||
<td class="p-1 px-2">
|
||||
<span class={`inline-block px-1.5 py-0.5 text-xs font-medium rounded ${
|
||||
resource.type === 'pbs' ? 'bg-indigo-100 dark:bg-indigo-900/50 text-indigo-700 dark:text-indigo-300' :
|
||||
resource.type === 'node' ? 'bg-purple-100 dark:bg-purple-900/50 text-purple-700 dark:text-purple-300' :
|
||||
@@ -133,7 +133,7 @@ export function ResourceTable(props: ResourceTableProps) {
|
||||
{resource.resourceType}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 py-1.5">
|
||||
<td class="p-1 px-2">
|
||||
<span class={`inline-block px-1.5 py-0.5 text-xs font-medium rounded ${
|
||||
resource.status === 'online' || resource.status === 'running' ?
|
||||
'bg-green-100 dark:bg-green-900/50 text-green-700 dark:text-green-300' :
|
||||
@@ -166,7 +166,7 @@ export function ResourceTable(props: ResourceTableProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<td class="px-3 py-1.5 text-center">
|
||||
<td class="p-1 px-2 text-center">
|
||||
<Show when={showMetric()} fallback={
|
||||
<span class="text-sm text-gray-400 dark:text-gray-500">-</span>
|
||||
}>
|
||||
@@ -198,7 +198,7 @@ export function ResourceTable(props: ResourceTableProps) {
|
||||
</For>
|
||||
|
||||
{/* Alerts column */}
|
||||
<td class="px-3 py-1.5 text-center">
|
||||
<td class="p-1 px-2 text-center">
|
||||
<Show when={resource.type === 'guest' && props.onToggleDisabled}>
|
||||
<button type="button"
|
||||
onClick={() => props.onToggleDisabled!(resource.id)}
|
||||
@@ -251,7 +251,7 @@ export function ResourceTable(props: ResourceTableProps) {
|
||||
</td>
|
||||
|
||||
{/* Actions column */}
|
||||
<td class="px-3 py-1.5">
|
||||
<td class="p-1 px-2">
|
||||
<div class="flex items-center justify-center gap-1">
|
||||
<Show when={!isEditing()} fallback={
|
||||
<>
|
||||
@@ -322,7 +322,7 @@ export function ResourceTable(props: ResourceTableProps) {
|
||||
|
||||
return (
|
||||
<tr class={`hover:bg-gray-50 dark:hover:bg-gray-900/50 transition-colors ${resource.disabled ? 'opacity-40' : ''}`}>
|
||||
<td class="px-3 py-1.5">
|
||||
<td class="p-1 px-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class={`text-sm font-medium ${resource.disabled ? 'text-gray-500 dark:text-gray-500' : 'text-gray-900 dark:text-gray-100'}`}>
|
||||
{resource.name}
|
||||
@@ -343,7 +343,7 @@ export function ResourceTable(props: ResourceTableProps) {
|
||||
</Show>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-3 py-1.5">
|
||||
<td class="p-1 px-2">
|
||||
<span class={`inline-block px-1.5 py-0.5 text-xs font-medium rounded ${
|
||||
resource.type === 'pbs' ? 'bg-indigo-100 dark:bg-indigo-900/50 text-indigo-700 dark:text-indigo-300' :
|
||||
resource.type === 'node' ? 'bg-purple-100 dark:bg-purple-900/50 text-purple-700 dark:text-purple-300' :
|
||||
@@ -354,7 +354,7 @@ export function ResourceTable(props: ResourceTableProps) {
|
||||
{resource.resourceType}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 py-1.5">
|
||||
<td class="p-1 px-2">
|
||||
<span class={`inline-block px-1.5 py-0.5 text-xs font-medium rounded ${
|
||||
resource.status === 'online' || resource.status === 'running' ?
|
||||
'bg-green-100 dark:bg-green-900/50 text-green-700 dark:text-green-300' :
|
||||
@@ -387,7 +387,7 @@ export function ResourceTable(props: ResourceTableProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<td class="px-3 py-1.5 text-center">
|
||||
<td class="p-1 px-2 text-center">
|
||||
<Show when={showMetric()} fallback={
|
||||
<span class="text-sm text-gray-400 dark:text-gray-500">-</span>
|
||||
}>
|
||||
@@ -419,7 +419,7 @@ export function ResourceTable(props: ResourceTableProps) {
|
||||
</For>
|
||||
|
||||
{/* Alerts column */}
|
||||
<td class="px-3 py-1.5 text-center">
|
||||
<td class="p-1 px-2 text-center">
|
||||
<Show when={resource.type === 'guest' && props.onToggleDisabled}>
|
||||
<button type="button"
|
||||
onClick={() => props.onToggleDisabled!(resource.id)}
|
||||
@@ -472,7 +472,7 @@ export function ResourceTable(props: ResourceTableProps) {
|
||||
</td>
|
||||
|
||||
{/* Actions column */}
|
||||
<td class="px-3 py-1.5">
|
||||
<td class="p-1 px-2">
|
||||
<div class="flex items-center justify-center gap-1">
|
||||
<Show when={!isEditing()} fallback={
|
||||
<>
|
||||
|
||||
@@ -621,8 +621,8 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="border-b border-gray-100 dark:border-gray-700/50">
|
||||
<td class="py-3 px-3 font-medium text-gray-700 dark:text-gray-300 text-sm">VMs & Containers</td>
|
||||
<td class="text-center px-3 py-3">
|
||||
<td class="p-1 px-2 font-medium text-gray-700 dark:text-gray-300 text-sm">VMs & Containers</td>
|
||||
<td class="text-center p-1 px-2">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -636,7 +636,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center px-3 py-3">
|
||||
<td class="text-center p-1 px-2">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -650,7 +650,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center px-3 py-3">
|
||||
<td class="text-center p-1 px-2">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -664,8 +664,8 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center px-3 py-3"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center px-3 py-3">
|
||||
<td class="text-center p-1 px-2"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center p-1 px-2">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -679,7 +679,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center px-3 py-3">
|
||||
<td class="text-center p-1 px-2">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -693,7 +693,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center px-3 py-3">
|
||||
<td class="text-center p-1 px-2">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -707,7 +707,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center px-3 py-3">
|
||||
<td class="text-center p-1 px-2">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -723,8 +723,8 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="border-b border-gray-100 dark:border-gray-700/50">
|
||||
<td class="py-3 px-3 font-medium text-gray-700 dark:text-gray-300 text-sm">Proxmox Nodes</td>
|
||||
<td class="text-center px-3 py-3">
|
||||
<td class="p-1 px-2 font-medium text-gray-700 dark:text-gray-300 text-sm">Proxmox Nodes</td>
|
||||
<td class="text-center p-1 px-2">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -738,7 +738,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center px-3 py-3">
|
||||
<td class="text-center p-1 px-2">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -752,7 +752,7 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center px-3 py-3">
|
||||
<td class="text-center p-1 px-2">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -766,18 +766,18 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center px-3 py-3"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center px-3 py-3"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center px-3 py-3"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center px-3 py-3"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center px-3 py-3"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center p-1 px-2"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center p-1 px-2"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center p-1 px-2"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center p-1 px-2"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center p-1 px-2"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-3 px-3 font-medium text-gray-700 dark:text-gray-300 text-sm">Storage</td>
|
||||
<td class="text-center px-3 py-3"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center px-3 py-3"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center px-3 py-3"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center px-3 py-3">
|
||||
<td class="p-1 px-2 font-medium text-gray-700 dark:text-gray-300 text-sm">Storage</td>
|
||||
<td class="text-center p-1 px-2"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center p-1 px-2"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center p-1 px-2"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center p-1 px-2">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -791,10 +791,10 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center px-3 py-3"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center px-3 py-3"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center px-3 py-3"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center px-3 py-3"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center p-1 px-2"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center p-1 px-2"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center p-1 px-2"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
<td class="text-center p-1 px-2"><span class="text-gray-400 dark:text-gray-500">-</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -893,14 +893,14 @@ const UnifiedBackups: Component = () => {
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200 dark:border-gray-700">
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">PBS Instance</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Status</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">CPU</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Memory</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Storage</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Datastores</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Backups</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Uptime</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider">PBS Instance</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider">Status</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider">CPU</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider">Memory</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider">Storage</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider">Datastores</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider">Backups</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider">Uptime</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@@ -952,7 +952,7 @@ const UnifiedBackups: Component = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<td class="px-2 py-0.5 whitespace-nowrap">
|
||||
<td class="p-0.5 px-1.5 whitespace-nowrap">
|
||||
<div class="flex items-center gap-1">
|
||||
<a
|
||||
href={pbs.host || `https://${pbs.name}:8007`}
|
||||
@@ -969,7 +969,7 @@ const UnifiedBackups: Component = () => {
|
||||
</Show>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 py-0.5 whitespace-nowrap">
|
||||
<td class="p-0.5 px-1.5 whitespace-nowrap">
|
||||
<div class="flex items-center gap-1">
|
||||
<span class={`h-2 w-2 rounded-full ${
|
||||
isOnline() ? 'bg-green-500' : 'bg-red-500'
|
||||
@@ -979,14 +979,14 @@ const UnifiedBackups: Component = () => {
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 py-0.5 w-[180px]">
|
||||
<td class="p-0.5 px-1.5 w-[180px]">
|
||||
<MetricBar
|
||||
value={cpuPercent()}
|
||||
label={`${cpuPercent()}%`}
|
||||
type="cpu"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-2 py-0.5 w-[180px]">
|
||||
<td class="p-0.5 px-1.5 w-[180px]">
|
||||
<MetricBar
|
||||
value={memPercent()}
|
||||
label={`${memPercent()}%`}
|
||||
@@ -994,7 +994,7 @@ const UnifiedBackups: Component = () => {
|
||||
type="memory"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-2 py-0.5 w-[180px]">
|
||||
<td class="p-0.5 px-1.5 w-[180px]">
|
||||
<MetricBar
|
||||
value={storage.percent}
|
||||
label={`${storage.percent}%`}
|
||||
@@ -1002,15 +1002,15 @@ const UnifiedBackups: Component = () => {
|
||||
type="disk"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-2 py-0.5 whitespace-nowrap text-center">
|
||||
<td class="p-0.5 px-1.5 whitespace-nowrap text-center">
|
||||
<span class="text-xs text-gray-700 dark:text-gray-300">
|
||||
{pbs.datastores?.length || 0}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-2 py-0.5 whitespace-nowrap text-center">
|
||||
<td class="p-0.5 px-1.5 whitespace-nowrap text-center">
|
||||
<span class="text-xs text-gray-700 dark:text-gray-300">{pbsBackups()}</span>
|
||||
</td>
|
||||
<td class="px-2 py-0.5 whitespace-nowrap">
|
||||
<td class="p-0.5 px-1.5 whitespace-nowrap">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">
|
||||
<Show when={isOnline() && pbs.uptime} fallback="-">
|
||||
{formatUptime(pbs.uptime)}
|
||||
@@ -1040,7 +1040,7 @@ const UnifiedBackups: Component = () => {
|
||||
<div class="flex items-center gap-1">
|
||||
<button type="button"
|
||||
onClick={() => setChartTimeRange(7)}
|
||||
class={`px-2 py-0.5 text-xs border rounded transition-colors ${
|
||||
class={`p-0.5 px-1.5 text-xs border rounded transition-colors ${
|
||||
chartTimeRange() === 7
|
||||
? 'bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 border-blue-300 dark:border-blue-700'
|
||||
: 'border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700'
|
||||
@@ -1050,7 +1050,7 @@ const UnifiedBackups: Component = () => {
|
||||
</button>
|
||||
<button type="button"
|
||||
onClick={() => setChartTimeRange(30)}
|
||||
class={`px-2 py-0.5 text-xs border rounded transition-colors ${
|
||||
class={`p-0.5 px-1.5 text-xs border rounded transition-colors ${
|
||||
chartTimeRange() === 30
|
||||
? 'bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 border-blue-300 dark:border-blue-700'
|
||||
: 'border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700'
|
||||
@@ -1060,7 +1060,7 @@ const UnifiedBackups: Component = () => {
|
||||
</button>
|
||||
<button type="button"
|
||||
onClick={() => setChartTimeRange(90)}
|
||||
class={`px-2 py-0.5 text-xs border rounded transition-colors ${
|
||||
class={`p-0.5 px-1.5 text-xs border rounded transition-colors ${
|
||||
chartTimeRange() === 90
|
||||
? 'bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 border-blue-300 dark:border-blue-700'
|
||||
: 'border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700'
|
||||
@@ -1070,7 +1070,7 @@ const UnifiedBackups: Component = () => {
|
||||
</button>
|
||||
<button type="button"
|
||||
onClick={() => setChartTimeRange(365)}
|
||||
class={`px-2 py-0.5 text-xs border rounded transition-colors ${
|
||||
class={`p-0.5 px-1.5 text-xs border rounded transition-colors ${
|
||||
chartTimeRange() === 365
|
||||
? 'bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 border-blue-300 dark:border-blue-700'
|
||||
: 'border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700'
|
||||
@@ -1086,7 +1086,7 @@ const UnifiedBackups: Component = () => {
|
||||
<Show when={selectedDateRange()}>
|
||||
<button type="button"
|
||||
onClick={() => setSelectedDateRange(null)}
|
||||
class="px-2 py-0.5 text-xs bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 rounded hover:bg-blue-200 dark:hover:bg-blue-800/50 transition-colors"
|
||||
class="p-0.5 px-1.5 text-xs bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 rounded hover:bg-blue-200 dark:hover:bg-blue-800/50 transition-colors"
|
||||
>
|
||||
Clear filter
|
||||
</button>
|
||||
@@ -1551,33 +1551,37 @@ const UnifiedBackups: Component = () => {
|
||||
{/* Desktop Table View */}
|
||||
<table class="backup-table hidden lg:table">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200 dark:border-gray-700">
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider" style="width: 150px;">
|
||||
Name
|
||||
<tr class="bg-gray-50 dark:bg-gray-700/50 text-gray-600 dark:text-gray-300 border-b border-gray-200 dark:border-gray-600">
|
||||
<th
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
onClick={() => handleSort('name')}
|
||||
style="width: 150px;"
|
||||
>
|
||||
Name {sortKey() === 'name' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||
</th>
|
||||
<th
|
||||
class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
onClick={() => handleSort('type')}
|
||||
style="width: 60px;"
|
||||
>
|
||||
Type {sortKey() === 'type' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||
</th>
|
||||
<th
|
||||
class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
onClick={() => handleSort('vmid')}
|
||||
style="width: 60px;"
|
||||
>
|
||||
VMID {sortKey() === 'vmid' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||
</th>
|
||||
<th
|
||||
class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
onClick={() => handleSort('node')}
|
||||
style="width: 100px;"
|
||||
>
|
||||
Node {sortKey() === 'node' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||
</th>
|
||||
<th
|
||||
class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
onClick={() => handleSort('backupTime')}
|
||||
style="width: 140px;"
|
||||
>
|
||||
@@ -1585,7 +1589,7 @@ const UnifiedBackups: Component = () => {
|
||||
</th>
|
||||
<Show when={backupTypeFilter() !== 'snapshot'}>
|
||||
<th
|
||||
class="sortable p-1 px-2 cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
onClick={() => handleSort('size')}
|
||||
style="width: 80px;"
|
||||
>
|
||||
@@ -1593,23 +1597,31 @@ const UnifiedBackups: Component = () => {
|
||||
</th>
|
||||
</Show>
|
||||
<th
|
||||
class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
onClick={() => handleSort('backupType')}
|
||||
style="width: 80px;"
|
||||
>
|
||||
Backup {sortKey() === 'backupType' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||
</th>
|
||||
<Show when={backupTypeFilter() === 'all' || backupTypeFilter() === 'remote'}>
|
||||
<th class="p-1 px-2 text-center" style="width: 60px;">
|
||||
Verified
|
||||
<th
|
||||
class="px-2 py-1.5 text-center text-[11px] sm:text-xs font-medium uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
onClick={() => handleSort('verified')}
|
||||
style="width: 60px;"
|
||||
>
|
||||
Verified {sortKey() === 'verified' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||
</th>
|
||||
</Show>
|
||||
<Show when={backupTypeFilter() !== 'snapshot'}>
|
||||
<th class="p-1 px-2" style="width: 150px;">
|
||||
Location
|
||||
<th
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
onClick={() => handleSort('storage')}
|
||||
style="width: 150px;"
|
||||
>
|
||||
Location {sortKey() === 'storage' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||
</th>
|
||||
</Show>
|
||||
<th class="p-1 px-2" style="width: 200px;">
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider" style="width: 200px;">
|
||||
Details
|
||||
</th>
|
||||
</tr>
|
||||
@@ -1619,7 +1631,7 @@ const UnifiedBackups: Component = () => {
|
||||
{(group) => (
|
||||
<>
|
||||
<tr class="bg-gray-50/50 dark:bg-gray-700/30">
|
||||
<td class="px-2 py-0.5 text-xs font-medium text-gray-600 dark:text-gray-400 whitespace-nowrap">
|
||||
<td class="p-0.5 px-1.5 text-xs font-medium text-gray-600 dark:text-gray-400 whitespace-nowrap">
|
||||
{group.label} ({group.items.length})
|
||||
</td>
|
||||
<td colspan={(() => {
|
||||
@@ -1628,16 +1640,16 @@ const UnifiedBackups: Component = () => {
|
||||
if (backupTypeFilter() === 'all' || backupTypeFilter() === 'remote') cols++; // Add Verified column
|
||||
if (backupTypeFilter() !== 'snapshot') cols++; // Add Location column
|
||||
return cols;
|
||||
})()} class="px-2 py-1 text-xs font-medium text-gray-500 dark:text-gray-400"></td>
|
||||
})()} class="p-0.5 px-1.5 text-xs font-medium text-gray-500 dark:text-gray-400"></td>
|
||||
</tr>
|
||||
<For each={group.items}>
|
||||
{(item) => (
|
||||
<tr class="border-t border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<td class="p-1 px-2">
|
||||
<td class="p-0.5 px-1.5 text-sm">
|
||||
{item.name || '-'}
|
||||
</td>
|
||||
<td class="p-1 px-2">
|
||||
<span class={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${
|
||||
<td class="p-0.5 px-1.5">
|
||||
<span class={`inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium ${
|
||||
item.type === 'VM'
|
||||
? 'bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300'
|
||||
: 'bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300'
|
||||
@@ -1645,20 +1657,20 @@ const UnifiedBackups: Component = () => {
|
||||
{item.type}
|
||||
</span>
|
||||
</td>
|
||||
<td class="p-1 px-2 font-medium">{item.vmid}</td>
|
||||
<td class="p-1 px-2 cursor-pointer hover:text-blue-600 dark:hover:text-blue-400">
|
||||
<td class="p-0.5 px-1.5 text-sm">{item.vmid}</td>
|
||||
<td class="p-0.5 px-1.5 text-sm cursor-pointer hover:text-blue-600 dark:hover:text-blue-400">
|
||||
{item.node}
|
||||
</td>
|
||||
<td class={`p-1 px-2 text-xs ${getAgeColorClass(item.backupTime)}`}>
|
||||
<td class={`p-0.5 px-1.5 text-xs ${getAgeColorClass(item.backupTime)}`}>
|
||||
{formatTime(item.backupTime * 1000)}
|
||||
</td>
|
||||
<Show when={backupTypeFilter() !== 'snapshot'}>
|
||||
<td class={`p-1 px-2 ${getSizeColor(item.size)}`}>
|
||||
<td class={`p-0.5 px-1.5 ${getSizeColor(item.size)}`}>
|
||||
{item.size ? formatBytes(item.size) : '-'}
|
||||
</td>
|
||||
</Show>
|
||||
<td class="p-1 px-2">
|
||||
<span class={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${
|
||||
<td class="p-0.5 px-1.5">
|
||||
<span class={`inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium ${
|
||||
item.backupType === 'snapshot'
|
||||
? 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/20 dark:text-yellow-300'
|
||||
: item.backupType === 'local'
|
||||
@@ -1669,7 +1681,7 @@ const UnifiedBackups: Component = () => {
|
||||
</span>
|
||||
</td>
|
||||
<Show when={backupTypeFilter() === 'all' || backupTypeFilter() === 'remote'}>
|
||||
<td class="p-1 px-2 text-center">
|
||||
<td class="p-0.5 px-1.5 text-center">
|
||||
{item.backupType === 'remote' ? (
|
||||
item.verified ? (
|
||||
<span title="PBS backup verified" class="text-green-500 dark:text-green-400">✓</span>
|
||||
@@ -1682,7 +1694,7 @@ const UnifiedBackups: Component = () => {
|
||||
</td>
|
||||
</Show>
|
||||
<Show when={backupTypeFilter() !== 'snapshot'}>
|
||||
<td class="p-1 px-2">
|
||||
<td class="p-0.5 px-1.5 text-sm">
|
||||
{item.storage || (item.datastore && (
|
||||
item.namespace && item.namespace !== 'root'
|
||||
? `${item.datastore}/${item.namespace}`
|
||||
@@ -1691,7 +1703,7 @@ const UnifiedBackups: Component = () => {
|
||||
</td>
|
||||
</Show>
|
||||
<td
|
||||
class="p-1 px-2 cursor-help"
|
||||
class="p-0.5 px-1.5 cursor-help"
|
||||
onMouseEnter={(e) => {
|
||||
const details = [];
|
||||
|
||||
|
||||
@@ -404,7 +404,7 @@ export function Dashboard(props: DashboardProps) {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<td class="px-2 py-0.5 whitespace-nowrap">
|
||||
<td class="p-1 px-2 whitespace-nowrap">
|
||||
<div class="flex items-center gap-1">
|
||||
<a
|
||||
href={node.host || `https://${node.name}:8006`}
|
||||
@@ -425,7 +425,7 @@ export function Dashboard(props: DashboardProps) {
|
||||
</Show>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 py-0.5 whitespace-nowrap">
|
||||
<td class="p-1 px-2 whitespace-nowrap">
|
||||
<div class="flex items-center gap-1">
|
||||
<span class={`h-2 w-2 rounded-full ${
|
||||
isOnline() ? 'bg-green-500' : 'bg-red-500'
|
||||
@@ -435,7 +435,7 @@ export function Dashboard(props: DashboardProps) {
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 py-0.5 w-[180px]">
|
||||
<td class="p-1 px-2 w-[180px]">
|
||||
<MetricBar
|
||||
value={cpuPercent()}
|
||||
label={`${cpuPercent()}%`}
|
||||
@@ -443,7 +443,7 @@ export function Dashboard(props: DashboardProps) {
|
||||
type="cpu"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-2 py-0.5 w-[180px]">
|
||||
<td class="p-1 px-2 w-[180px]">
|
||||
<MetricBar
|
||||
value={memPercent()}
|
||||
label={`${memPercent()}%`}
|
||||
@@ -451,7 +451,7 @@ export function Dashboard(props: DashboardProps) {
|
||||
type="memory"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-2 py-0.5 w-[180px]">
|
||||
<td class="p-1 px-2 w-[180px]">
|
||||
<MetricBar
|
||||
value={diskPercent()}
|
||||
label={`${diskPercent()}%`}
|
||||
@@ -459,13 +459,13 @@ export function Dashboard(props: DashboardProps) {
|
||||
type="disk"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-2 py-0.5 whitespace-nowrap text-center">
|
||||
<td class="p-1 px-2 whitespace-nowrap text-center">
|
||||
<span class="text-xs text-gray-700 dark:text-gray-300">{nodeVMs()}</span>
|
||||
</td>
|
||||
<td class="px-2 py-0.5 whitespace-nowrap text-center">
|
||||
<td class="p-1 px-2 whitespace-nowrap text-center">
|
||||
<span class="text-xs text-gray-700 dark:text-gray-300">{nodeContainers()}</span>
|
||||
</td>
|
||||
<td class="px-2 py-0.5 whitespace-nowrap">
|
||||
<td class="p-1 px-2 whitespace-nowrap">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">
|
||||
{formatUptime(node.uptime)}
|
||||
</span>
|
||||
@@ -554,9 +554,9 @@ export function Dashboard(props: DashboardProps) {
|
||||
>
|
||||
<table class="w-full min-w-[900px] table-fixed">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200 dark:border-gray-700">
|
||||
<tr class="bg-gray-50 dark:bg-gray-700/50 text-gray-600 dark:text-gray-300 border-b border-gray-200 dark:border-gray-600">
|
||||
<th
|
||||
class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-[200px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500"
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[200px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500"
|
||||
onClick={() => handleSort('name')}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleSort('name')}
|
||||
tabindex="0"
|
||||
@@ -566,61 +566,61 @@ export function Dashboard(props: DashboardProps) {
|
||||
Name {sortKey() === 'name' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||
</th>
|
||||
<th
|
||||
class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-[60px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[60px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
onClick={() => handleSort('type')}
|
||||
>
|
||||
Type {sortKey() === 'type' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||
</th>
|
||||
<th
|
||||
class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-[70px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[70px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
onClick={() => handleSort('vmid')}
|
||||
>
|
||||
VMID {sortKey() === 'vmid' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||
</th>
|
||||
<th
|
||||
class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-[100px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[100px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
onClick={() => handleSort('uptime')}
|
||||
>
|
||||
Uptime {sortKey() === 'uptime' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||
</th>
|
||||
<th
|
||||
class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-[140px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[140px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
onClick={() => handleSort('cpu')}
|
||||
>
|
||||
CPU {sortKey() === 'cpu' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||
</th>
|
||||
<th
|
||||
class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-[140px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[140px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
onClick={() => handleSort('memory')}
|
||||
>
|
||||
Memory {sortKey() === 'memory' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||
</th>
|
||||
<th
|
||||
class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-[140px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[140px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
onClick={() => handleSort('disk')}
|
||||
>
|
||||
Disk {sortKey() === 'disk' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||
</th>
|
||||
<th
|
||||
class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-[90px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[90px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
onClick={() => handleSort('diskRead')}
|
||||
>
|
||||
Disk Read {sortKey() === 'diskRead' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||
</th>
|
||||
<th
|
||||
class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-[90px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[90px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
onClick={() => handleSort('diskWrite')}
|
||||
>
|
||||
Disk Write {sortKey() === 'diskWrite' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||
</th>
|
||||
<th
|
||||
class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-[90px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[90px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
onClick={() => handleSort('networkIn')}
|
||||
>
|
||||
Net In {sortKey() === 'networkIn' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||
</th>
|
||||
<th
|
||||
class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-[90px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider w-[90px] cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
onClick={() => handleSort('networkOut')}
|
||||
>
|
||||
Net Out {sortKey() === 'networkOut' && (sortDirection() === 'asc' ? '▲' : '▼')}
|
||||
@@ -633,7 +633,7 @@ export function Dashboard(props: DashboardProps) {
|
||||
<>
|
||||
<Show when={node}>
|
||||
<tr class="bg-gray-50/50 dark:bg-gray-700/30">
|
||||
<td class="px-2 py-0.5 text-xs font-medium text-gray-600 dark:text-gray-400 w-[200px]">
|
||||
<td class="p-1 px-2 text-xs font-medium text-gray-600 dark:text-gray-400 w-[200px]">
|
||||
<a
|
||||
href={nodeHostMap()[node] || (node.includes(':') ? `https://${node}` : `https://${node}:8006`)}
|
||||
target="_blank"
|
||||
@@ -644,7 +644,7 @@ export function Dashboard(props: DashboardProps) {
|
||||
{node}
|
||||
</a>
|
||||
</td>
|
||||
<td colspan="10" class="px-2 py-0.5"></td>
|
||||
<td colspan="10" class="p-1 px-2"></td>
|
||||
</tr>
|
||||
</Show>
|
||||
<For each={guests} fallback={<></>}>
|
||||
|
||||
@@ -97,12 +97,12 @@ export function GuestRow(props: GuestRowProps) {
|
||||
<div class="flex items-center gap-2">
|
||||
{/* Status indicator */}
|
||||
<span class={`h-2 w-2 rounded-full flex-shrink-0 ${
|
||||
isRunning() ? 'bg-green-500' : 'bg-red-500'
|
||||
isRunning() ? 'bg-green-500' : 'bg-gray-400'
|
||||
}`} title={props.guest.status}></span>
|
||||
|
||||
{/* Name - clickable if custom URL is set */}
|
||||
<Show when={customUrl()} fallback={
|
||||
<span class="font-medium text-gray-900 dark:text-gray-100 truncate" title={props.guest.name}>
|
||||
<span class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate" title={props.guest.name}>
|
||||
{props.guest.name}
|
||||
</span>
|
||||
}>
|
||||
@@ -110,7 +110,7 @@ export function GuestRow(props: GuestRowProps) {
|
||||
href={customUrl()}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="font-medium text-gray-900 dark:text-gray-100 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-150 cursor-pointer truncate"
|
||||
class="text-sm font-medium text-gray-900 dark:text-gray-100 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-150 cursor-pointer truncate"
|
||||
title={`${props.guest.name} - Click to open custom URL`}
|
||||
>
|
||||
{props.guest.name}
|
||||
|
||||
@@ -217,12 +217,12 @@ export function GuestURLs(props: GuestURLsProps) {
|
||||
|
||||
return (
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-900/50 transition-colors">
|
||||
<td class="px-3 py-1.5">
|
||||
<td class="p-1 px-2">
|
||||
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
{guest.name}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-3 py-1.5">
|
||||
<td class="p-1 px-2">
|
||||
<span class={`inline-block px-1.5 py-0.5 text-xs font-medium rounded ${
|
||||
guest.type === 'qemu'
|
||||
? 'bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300'
|
||||
@@ -231,10 +231,10 @@ export function GuestURLs(props: GuestURLsProps) {
|
||||
{guest.type === 'qemu' ? 'VM' : 'LXC'}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 py-1.5 text-sm text-gray-600 dark:text-gray-400">
|
||||
<td class="p-1 px-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
{guest.vmid}
|
||||
</td>
|
||||
<td class="px-3 py-1.5">
|
||||
<td class="p-1 px-2">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="https://192.168.1.100:8006"
|
||||
@@ -245,7 +245,7 @@ export function GuestURLs(props: GuestURLsProps) {
|
||||
focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-3 py-1.5">
|
||||
<td class="p-1 px-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<Show when={url}>
|
||||
<a
|
||||
|
||||
@@ -109,12 +109,10 @@ const Storage: Component = () => {
|
||||
});
|
||||
|
||||
const getProgressBarColor = (usage: number) => {
|
||||
// Match MetricBar component styling - use the same disk/generic logic
|
||||
// Match MetricBar component styling exactly - disk type thresholds
|
||||
if (usage >= 90) return 'bg-red-500/60 dark:bg-red-500/50';
|
||||
if (usage >= 80) return 'bg-yellow-500/60 dark:bg-yellow-500/50';
|
||||
if (usage >= 70) return 'bg-amber-500/60 dark:bg-amber-500/50';
|
||||
if (usage >= 60) return 'bg-yellow-500/60 dark:bg-yellow-500/50';
|
||||
return 'bg-emerald-500/60 dark:bg-emerald-500/50';
|
||||
return 'bg-green-500/60 dark:bg-green-500/50';
|
||||
};
|
||||
|
||||
const resetFilters = () => {
|
||||
@@ -266,20 +264,20 @@ const Storage: Component = () => {
|
||||
`}</style>
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200 dark:border-gray-700">
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Storage</th>
|
||||
<tr class="bg-gray-50 dark:bg-gray-700/50 text-gray-600 dark:text-gray-300 border-b border-gray-200 dark:border-gray-600">
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider">Storage</th>
|
||||
<Show when={viewMode() === 'node'}>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider hidden sm:table-cell">Node</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider hidden sm:table-cell">Node</th>
|
||||
</Show>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider hidden md:table-cell">Type</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider hidden lg:table-cell">Content</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider hidden sm:table-cell">Status</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider hidden md:table-cell">Type</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider hidden lg:table-cell">Content</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider hidden sm:table-cell">Status</th>
|
||||
<Show when={viewMode() === 'node'}>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider hidden lg:table-cell">Shared</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider hidden lg:table-cell">Shared</th>
|
||||
</Show>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider min-w-[100px] sm:min-w-[150px] md:min-w-[200px]">Usage</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider hidden sm:table-cell">Free</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Total</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider min-w-[100px] sm:min-w-[150px] md:min-w-[200px]">Usage</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider hidden sm:table-cell">Free</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@@ -289,7 +287,7 @@ const Storage: Component = () => {
|
||||
{/* Group Header */}
|
||||
<Show when={viewMode() === 'node'}>
|
||||
<tr class="bg-gray-50/50 dark:bg-gray-700/30">
|
||||
<td class="px-2 py-0.5 text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
<td class="p-0.5 px-1.5 text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
<a
|
||||
href={nodeHostMap()[groupName] || (groupName.includes(':') ? `https://${groupName}` : `https://${groupName}:8006`)}
|
||||
target="_blank"
|
||||
@@ -300,7 +298,7 @@ const Storage: Component = () => {
|
||||
{groupName}
|
||||
</a>
|
||||
</td>
|
||||
<td class="px-2 py-0.5 text-[10px] text-gray-500 dark:text-gray-400" colspan="8">
|
||||
<td class="p-0.5 px-1.5 text-[10px] text-gray-500 dark:text-gray-400" colspan="8">
|
||||
{getTotalByNode(storages).total > 0 && (
|
||||
<span>
|
||||
{formatBytes(getTotalByNode(storages).used)} / {formatBytes(getTotalByNode(storages).total)} ({calculateOverallUsage(storages).toFixed(1)}%)
|
||||
@@ -321,9 +319,9 @@ const Storage: Component = () => {
|
||||
|
||||
return (
|
||||
<tr class={`${rowClass} hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors`}>
|
||||
<td class="px-2 py-0.5">
|
||||
<td class="p-0.5 px-1.5">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-gray-900 dark:text-gray-100">
|
||||
<span class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
{storage.name}
|
||||
</span>
|
||||
<Show when={alertStyles.hasAlert}>
|
||||
@@ -337,9 +335,9 @@ const Storage: Component = () => {
|
||||
</div>
|
||||
</td>
|
||||
<Show when={viewMode() === 'node'}>
|
||||
<td class="p-1 px-2 text-xs text-gray-600 dark:text-gray-400 hidden sm:table-cell">{storage.node}</td>
|
||||
<td class="p-0.5 px-1.5 text-xs text-gray-600 dark:text-gray-400 hidden sm:table-cell">{storage.node}</td>
|
||||
</Show>
|
||||
<td class="p-1 px-2 hidden md:table-cell">
|
||||
<td class="p-0.5 px-1.5 hidden md:table-cell">
|
||||
<span class={`inline-block px-1.5 py-0.5 text-[10px] font-medium rounded ${
|
||||
storage.type === 'dir' ? 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300' :
|
||||
storage.type === 'pbs' ? 'bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300' :
|
||||
@@ -348,10 +346,10 @@ const Storage: Component = () => {
|
||||
{storage.type}
|
||||
</span>
|
||||
</td>
|
||||
<td class="p-1 px-2 text-xs text-gray-600 dark:text-gray-400 hidden lg:table-cell">
|
||||
<td class="p-0.5 px-1.5 text-xs text-gray-600 dark:text-gray-400 hidden lg:table-cell">
|
||||
{storage.content || '-'}
|
||||
</td>
|
||||
<td class="p-1 px-2 text-xs hidden sm:table-cell">
|
||||
<td class="p-0.5 px-1.5 text-xs hidden sm:table-cell">
|
||||
<span class={`${
|
||||
storage.status === 'available' ? 'text-green-600 dark:text-green-400' :
|
||||
'text-red-600 dark:text-red-400'
|
||||
@@ -360,27 +358,26 @@ const Storage: Component = () => {
|
||||
</span>
|
||||
</td>
|
||||
<Show when={viewMode() === 'node'}>
|
||||
<td class="p-1 px-2 text-xs text-gray-600 dark:text-gray-400 hidden lg:table-cell">
|
||||
<td class="p-0.5 px-1.5 text-xs text-gray-600 dark:text-gray-400 hidden lg:table-cell">
|
||||
{storage.shared ? '✓' : '-'}
|
||||
</td>
|
||||
</Show>
|
||||
|
||||
<td class="px-2 py-0.5">
|
||||
<td class="p-0.5 px-1.5">
|
||||
<div class="relative w-full h-3.5 rounded overflow-hidden bg-gray-200 dark:bg-gray-600">
|
||||
<div
|
||||
class={`absolute top-0 left-0 h-full ${getProgressBarColor(usagePercent)}`}
|
||||
style={{ width: `${usagePercent}%` }}
|
||||
/>
|
||||
<span class="absolute inset-0 flex items-center justify-center text-[10px] font-medium text-gray-800 dark:text-gray-100 leading-none">
|
||||
<span class="truncate px-1">
|
||||
<span class="sm:hidden">{usagePercent.toFixed(0)}%</span>
|
||||
<span class="hidden sm:inline">{formatBytes(storage.used || 0)} / {formatBytes(storage.total || 0)} ({usagePercent.toFixed(1)}%)</span>
|
||||
<span class="whitespace-nowrap px-0.5">
|
||||
{usagePercent.toFixed(0)}% ({formatBytes(storage.used || 0)}/{formatBytes(storage.total || 0)})
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-1 px-2 text-xs hidden sm:table-cell">{formatBytes(storage.free || 0)}</td>
|
||||
<td class="p-1 px-2 text-xs">{formatBytes(storage.total || 0)}</td>
|
||||
<td class="p-0.5 px-1.5 text-xs hidden sm:table-cell">{formatBytes(storage.free || 0)}</td>
|
||||
<td class="p-0.5 px-1.5 text-xs">{formatBytes(storage.total || 0)}</td>
|
||||
</tr>
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -90,22 +90,22 @@ export const NodeSummaryTable: Component<NodeSummaryTableProps> = (props) => {
|
||||
<div class="overflow-x-auto" style="overflow-x: auto;">
|
||||
<table style="width: 1000px; table-layout: fixed;">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200 dark:border-gray-700">
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider" style="width: 200px; white-space: nowrap;">
|
||||
<tr class="bg-gray-50 dark:bg-gray-700/50 text-gray-600 dark:text-gray-300 border-b border-gray-200 dark:border-gray-600">
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider" style="width: 200px; white-space: nowrap;">
|
||||
{props.currentTab === 'backups' ? 'Node / PBS' : 'Node'}
|
||||
</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider" style="width: 80px; white-space: nowrap;">Status</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider" style="width: 200px; white-space: nowrap;">CPU</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider" style="width: 200px; white-space: nowrap;">Memory</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider" style="width: 200px; white-space: nowrap;">
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider" style="width: 80px; white-space: nowrap;">Status</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider" style="width: 200px; white-space: nowrap;">CPU</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider" style="width: 200px; white-space: nowrap;">Memory</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider" style="width: 200px; white-space: nowrap;">
|
||||
{props.currentTab === 'backups' && props.pbsInstances ? 'Storage / Disk' : 'Disk'}
|
||||
</th>
|
||||
<For each={getCountHeader()}>
|
||||
{(header) => (
|
||||
<th class="px-2 py-1 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider" style="width: 80px; white-space: nowrap;">{header}</th>
|
||||
<th class="px-2 py-1.5 text-center text-[11px] sm:text-xs font-medium uppercase tracking-wider" style="width: 80px; white-space: nowrap;">{header}</th>
|
||||
)}
|
||||
</For>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider" style="width: 100px; white-space: nowrap;">Uptime</th>
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider" style="width: 100px; white-space: nowrap;">Uptime</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@@ -172,7 +172,7 @@ export const NodeSummaryTable: Component<NodeSummaryTableProps> = (props) => {
|
||||
href={isPVE ? (node!.host || `https://${node!.name}:8006`) : (pbs!.host || `https://${pbs!.name}:8007`)}
|
||||
target="_blank"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
class="font-medium text-xs text-gray-900 dark:text-gray-100 hover:text-blue-600 dark:hover:text-blue-400 whitespace-nowrap"
|
||||
class="font-medium text-[11px] text-gray-900 dark:text-gray-100 hover:text-blue-600 dark:hover:text-blue-400 whitespace-nowrap"
|
||||
>
|
||||
{item.data.name}
|
||||
</a>
|
||||
|
||||
@@ -91,29 +91,29 @@ export const PBSNodeTable: Component<PBSNodeTableProps> = (props) => {
|
||||
`}</style>
|
||||
<table class="w-full" style="min-width: 1000px;">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200 dark:border-gray-700">
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider whitespace-nowrap" style="min-width: 200px;">
|
||||
<tr class="bg-gray-50 dark:bg-gray-700/50 text-gray-600 dark:text-gray-300 border-b border-gray-200 dark:border-gray-600">
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider whitespace-nowrap" style="min-width: 200px;">
|
||||
PBS Instances
|
||||
</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider whitespace-nowrap" style="min-width: 80px;">
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider whitespace-nowrap" style="min-width: 80px;">
|
||||
Status
|
||||
</th>
|
||||
<th class="px-2 py-1 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider whitespace-nowrap" style="min-width: 100px;">
|
||||
<th class="px-2 py-1.5 text-center text-[11px] sm:text-xs font-medium uppercase tracking-wider whitespace-nowrap" style="min-width: 100px;">
|
||||
Datastores
|
||||
</th>
|
||||
<th class="px-2 py-1 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider whitespace-nowrap" style="min-width: 80px;">
|
||||
<th class="px-2 py-1.5 text-center text-[11px] sm:text-xs font-medium uppercase tracking-wider whitespace-nowrap" style="min-width: 80px;">
|
||||
Backups
|
||||
</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider whitespace-nowrap" style="min-width: 180px;">
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider whitespace-nowrap" style="min-width: 180px;">
|
||||
CPU
|
||||
</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider whitespace-nowrap" style="min-width: 180px;">
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider whitespace-nowrap" style="min-width: 180px;">
|
||||
Memory
|
||||
</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider whitespace-nowrap" style="min-width: 180px;">
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider whitespace-nowrap" style="min-width: 180px;">
|
||||
Storage Used
|
||||
</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider whitespace-nowrap">
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider whitespace-nowrap">
|
||||
Uptime
|
||||
</th>
|
||||
</tr>
|
||||
@@ -164,7 +164,7 @@ export const PBSNodeTable: Component<PBSNodeTableProps> = (props) => {
|
||||
isClickable && props.onNodeClick(pbs.name);
|
||||
}}
|
||||
>
|
||||
<td class="px-2 py-0.5 whitespace-nowrap">
|
||||
<td class="p-1 px-2 whitespace-nowrap">
|
||||
<div class="flex items-center gap-2">
|
||||
<Show when={hasDatastoresWithNamespaces()}>
|
||||
<button
|
||||
@@ -198,22 +198,22 @@ export const PBSNodeTable: Component<PBSNodeTableProps> = (props) => {
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 py-0.5">
|
||||
<td class="p-1 px-2">
|
||||
<span class={`text-xs ${isOnline() ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'}`}>
|
||||
{pbs.status}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-2 py-0.5 text-center">
|
||||
<td class="p-1 px-2 text-center">
|
||||
<span class="text-xs font-medium text-gray-700 dark:text-gray-300">
|
||||
{pbs.datastores?.length || 0}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-2 py-0.5 text-center">
|
||||
<td class="p-1 px-2 text-center">
|
||||
<span class="text-xs font-medium text-gray-700 dark:text-gray-300">
|
||||
{props.backupCounts?.[pbs.name] || 0}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-2 py-0.5" style="min-width: 180px;">
|
||||
<td class="p-1 px-2" style="min-width: 180px;">
|
||||
<Show when={isOnline()} fallback={<span class="text-xs text-gray-400">-</span>}>
|
||||
<MetricBar
|
||||
value={cpuPercent()}
|
||||
@@ -222,7 +222,7 @@ export const PBSNodeTable: Component<PBSNodeTableProps> = (props) => {
|
||||
/>
|
||||
</Show>
|
||||
</td>
|
||||
<td class="px-2 py-0.5" style="min-width: 180px;">
|
||||
<td class="p-1 px-2" style="min-width: 180px;">
|
||||
<Show when={isOnline()} fallback={<span class="text-xs text-gray-400">-</span>}>
|
||||
<MetricBar
|
||||
value={memPercent()}
|
||||
@@ -232,7 +232,7 @@ export const PBSNodeTable: Component<PBSNodeTableProps> = (props) => {
|
||||
/>
|
||||
</Show>
|
||||
</td>
|
||||
<td class="px-2 py-0.5" style="min-width: 180px;">
|
||||
<td class="p-1 px-2" style="min-width: 180px;">
|
||||
<Show when={isOnline() && totalStorage().total > 0} fallback={<span class="text-xs text-gray-400">-</span>}>
|
||||
<MetricBar
|
||||
value={totalStorage().percent}
|
||||
@@ -242,7 +242,7 @@ export const PBSNodeTable: Component<PBSNodeTableProps> = (props) => {
|
||||
/>
|
||||
</Show>
|
||||
</td>
|
||||
<td class="px-2 py-0.5">
|
||||
<td class="p-1 px-2">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">
|
||||
<Show when={isOnline() && pbs.uptime} fallback="-">
|
||||
{formatUptime(pbs.uptime)}
|
||||
|
||||
@@ -157,33 +157,33 @@ export const PVENodeTable: Component<PVENodeTableProps> = (props) => {
|
||||
`}</style>
|
||||
<table class="w-full" style="min-width: 1000px;">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200 dark:border-gray-700">
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider whitespace-nowrap" style="min-width: 200px;">
|
||||
<tr class="bg-gray-50 dark:bg-gray-700/50 text-gray-600 dark:text-gray-300 border-b border-gray-200 dark:border-gray-600">
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider whitespace-nowrap" style="min-width: 200px;">
|
||||
PVE Nodes
|
||||
</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider whitespace-nowrap" style="min-width: 80px;">
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider whitespace-nowrap" style="min-width: 80px;">
|
||||
Status
|
||||
</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider whitespace-nowrap" style="min-width: 100px;">
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider whitespace-nowrap" style="min-width: 100px;">
|
||||
Cluster
|
||||
</th>
|
||||
<For each={getCountHeaders()}>
|
||||
{(header) => (
|
||||
<th class="px-2 py-1 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider whitespace-nowrap" style="min-width: 80px;">
|
||||
<th class="px-2 py-1.5 text-center text-[11px] sm:text-xs font-medium uppercase tracking-wider whitespace-nowrap" style="min-width: 80px;">
|
||||
{header}
|
||||
</th>
|
||||
)}
|
||||
</For>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider whitespace-nowrap" style="min-width: 180px;">
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider whitespace-nowrap" style="min-width: 180px;">
|
||||
CPU
|
||||
</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider whitespace-nowrap" style="min-width: 180px;">
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider whitespace-nowrap" style="min-width: 180px;">
|
||||
Memory
|
||||
</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider whitespace-nowrap" style="min-width: 180px;">
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider whitespace-nowrap" style="min-width: 180px;">
|
||||
Storage
|
||||
</th>
|
||||
<th class="px-2 py-1 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider whitespace-nowrap">
|
||||
<th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider whitespace-nowrap">
|
||||
Uptime
|
||||
</th>
|
||||
</tr>
|
||||
@@ -224,7 +224,7 @@ export const PVENodeTable: Component<PVENodeTableProps> = (props) => {
|
||||
`}
|
||||
onClick={() => props.onNodeClick(node.name)}
|
||||
>
|
||||
<td class="px-2 py-0.5 whitespace-nowrap">
|
||||
<td class="p-1 px-2 whitespace-nowrap">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class={`h-2 w-2 rounded-full flex-shrink-0 ${isOnline() ? 'bg-green-500' : 'bg-red-500'}`}></span>
|
||||
<span class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate" title={node.name}>{node.name}</span>
|
||||
@@ -235,26 +235,26 @@ export const PVENodeTable: Component<PVENodeTableProps> = (props) => {
|
||||
</Show>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 py-0.5">
|
||||
<td class="p-1 px-2">
|
||||
<span class={`text-xs whitespace-nowrap ${isOnline() ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'}`}>
|
||||
{node.status}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-2 py-0.5">
|
||||
<td class="p-1 px-2">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400 block truncate" title={node.clusterName || ''}>
|
||||
{node.clusterName || '-'}
|
||||
</span>
|
||||
</td>
|
||||
<For each={counts}>
|
||||
{(count) => (
|
||||
<td class="px-2 py-0.5 text-center">
|
||||
<td class="p-1 px-2 text-center">
|
||||
<span class="text-xs font-medium text-gray-700 dark:text-gray-300 whitespace-nowrap">
|
||||
{count}
|
||||
</span>
|
||||
</td>
|
||||
)}
|
||||
</For>
|
||||
<td class="px-2 py-0.5" style="min-width: 180px;">
|
||||
<td class="p-1 px-2" style="min-width: 180px;">
|
||||
<Show when={isOnline()} fallback={<span class="text-xs text-gray-400">-</span>}>
|
||||
<MetricBar
|
||||
value={cpuPercent()}
|
||||
@@ -264,7 +264,7 @@ export const PVENodeTable: Component<PVENodeTableProps> = (props) => {
|
||||
/>
|
||||
</Show>
|
||||
</td>
|
||||
<td class="px-2 py-0.5" style="min-width: 180px;">
|
||||
<td class="p-1 px-2" style="min-width: 180px;">
|
||||
<Show when={isOnline()} fallback={<span class="text-xs text-gray-400">-</span>}>
|
||||
<MetricBar
|
||||
value={memPercent()}
|
||||
@@ -274,7 +274,7 @@ export const PVENodeTable: Component<PVENodeTableProps> = (props) => {
|
||||
/>
|
||||
</Show>
|
||||
</td>
|
||||
<td class="px-2 py-0.5" style="min-width: 180px;">
|
||||
<td class="p-1 px-2" style="min-width: 180px;">
|
||||
<Show when={isOnline()} fallback={<span class="text-xs text-gray-400">-</span>}>
|
||||
<MetricBar
|
||||
value={storagePercent()}
|
||||
@@ -284,7 +284,7 @@ export const PVENodeTable: Component<PVENodeTableProps> = (props) => {
|
||||
/>
|
||||
</Show>
|
||||
</td>
|
||||
<td class="px-2 py-0.5">
|
||||
<td class="p-1 px-2">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400 whitespace-nowrap">
|
||||
<Show when={isOnline() && node.uptime} fallback="-">
|
||||
{formatUptime(node.uptime)}
|
||||
|
||||
Reference in New Issue
Block a user