Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Volume table component #4841

Merged
merged 4 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/renderer/src/lib/table/Table.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ function setGridColumns() {
? 'justify-self-end'
: column.info.align === 'center'
? 'justify-self-center'
: 'justify-self-start'} self-center overflow-hidden"
: 'justify-self-start'} self-center overflow-hidden max-w-full"
role="cell">
{#if column.info.renderer}
<svelte:component this="{column.info.renderer}" object="{object}" />
Expand Down
8 changes: 8 additions & 0 deletions packages/renderer/src/lib/volume/VolumeColumnActions.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script lang="ts">
import VolumeActions from './VolumeActions.svelte';
import type { VolumeInfoUI } from './VolumeInfoUI';

export let object: VolumeInfoUI;
deboer-tim marked this conversation as resolved.
Show resolved Hide resolved
</script>

<VolumeActions volume="{object}" />
9 changes: 9 additions & 0 deletions packages/renderer/src/lib/volume/VolumeColumnAge.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script lang="ts">
import type { VolumeInfoUI } from './VolumeInfoUI';

export let object: VolumeInfoUI;
feloy marked this conversation as resolved.
Show resolved Hide resolved
</script>

<div class="text-sm text-gray-700">
{object.age}
</div>
10 changes: 10 additions & 0 deletions packages/renderer/src/lib/volume/VolumeColumnEnvironment.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script lang="ts">
import ProviderInfo from '../ui/ProviderInfo.svelte';
import type { VolumeInfoUI } from './VolumeInfoUI';

export let object: VolumeInfoUI;
feloy marked this conversation as resolved.
Show resolved Hide resolved
</script>

<div class="text-xs rounded-md text-gray-500">
<ProviderInfo provider="{object.engineName}" context="{object.engineId}" />
</div>
16 changes: 16 additions & 0 deletions packages/renderer/src/lib/volume/VolumeColumnName.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script lang="ts">
feloy marked this conversation as resolved.
Show resolved Hide resolved
import { router } from 'tinro';
import type { VolumeInfoUI } from './VolumeInfoUI';

export let object: VolumeInfoUI;

function openDetailsVolume(volume: VolumeInfoUI) {
router.goto(`/volumes/${encodeURI(volume.name)}/${encodeURI(volume.engineId)}/summary`);
}
</script>

<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="hover:cursor-pointer flex text-sm text-gray-300" on:click="{() => openDetailsVolume(object)}">
{object.shortName}
</div>
9 changes: 9 additions & 0 deletions packages/renderer/src/lib/volume/VolumeColumnSize.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script lang="ts">
import type { VolumeInfoUI } from './VolumeInfoUI';

export let object: VolumeInfoUI;
</script>

<div class="text-sm text-gray-700">
{object.humanSize}
</div>
9 changes: 9 additions & 0 deletions packages/renderer/src/lib/volume/VolumeColumnStatus.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script lang="ts">
import StatusIcon from '../images/StatusIcon.svelte';
import VolumeIcon from '../images/VolumeIcon.svelte';
import type { VolumeInfoUI } from './VolumeInfoUI';

export let object: VolumeInfoUI;
</script>

<StatusIcon icon="{VolumeIcon}" status="{object.inUse ? 'USED' : 'UNUSED'}" />
1 change: 1 addition & 0 deletions packages/renderer/src/lib/volume/VolumeInfoUI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface VolumeInfoUI {
driver: string;
created: string;
age: string;
size: number;
humanSize: string;
engineId: string;
engineName: string;
Expand Down
159 changes: 63 additions & 96 deletions packages/renderer/src/lib/volume/VolumesList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,32 @@ import { VolumeUtils } from './volume-utils';
import NoContainerEngineEmptyScreen from '../image/NoContainerEngineEmptyScreen.svelte';
import VolumeEmptyScreen from './VolumeEmptyScreen.svelte';
import FilteredEmptyScreen from '../ui/FilteredEmptyScreen.svelte';
import VolumeActions from './VolumeActions.svelte';
import VolumeIcon from '../images/VolumeIcon.svelte';
import StatusIcon from '../images/StatusIcon.svelte';
import Prune from '../engine/Prune.svelte';
import moment from 'moment';
import type { EngineInfoUI } from '../engine/EngineInfoUI';
import Checkbox from '../ui/Checkbox.svelte';
import Button from '../ui/Button.svelte';
import { faPieChart, faPlusCircle, faTrash } from '@fortawesome/free-solid-svg-icons';
import Table from '../table/Table.svelte';
import { Column, Row } from '../table/table';
import VolumeColumnStatus from './VolumeColumnStatus.svelte';
import VolumeColumnName from './VolumeColumnName.svelte';
import VolumeColumnEnvironment from './VolumeColumnEnvironment.svelte';
import VolumeColumnSize from './VolumeColumnSize.svelte';
import VolumeColumnAge from './VolumeColumnAge.svelte';
import VolumeColumnActions from './VolumeColumnActions.svelte';

export let searchTerm = '';
$: searchPattern.set(searchTerm);

let volumes: VolumeInfoUI[] = [];
let multipleEngines = false;
let enginesList: EngineInfoUI[];

$: providerConnections = $providerInfos
.map(provider => provider.containerConnections)
.flat()
.filter(providerContainerConnection => providerContainerConnection.status === 'started');

// number of selected items in the list
$: selectedItemsNumber = volumes.filter(volume => !volume.inUse).filter(volume => volume.selected).length;

// do we need to unselect all checkboxes if we don't have all items being selected ?
$: selectedAllCheckboxes = volumes.filter(volume => !volume.inUse).every(volume => volume.selected);

const volumeUtils = new VolumeUtils();

let volumesUnsubscribe: Unsubscriber;
Expand All @@ -60,11 +58,7 @@ onMount(async () => {
const uniqueEngines = engines.filter(
(engine, index, self) => index === self.findIndex(t => t.name === engine.name),
);
if (uniqueEngines.length > 1) {
multipleEngines = true;
} else {
multipleEngines = false;
}

// Set the engines to the global variable for the Prune functionality button
enginesList = uniqueEngines;

Expand Down Expand Up @@ -96,13 +90,6 @@ onDestroy(() => {
}
});

function toggleAllVolumes(checked: boolean) {
const toggleVolumes = volumes;
// filter out all volumes used by a container
toggleVolumes.filter(volume => !volume.inUse).forEach(volume => (volume.selected = checked));
volumes = toggleVolumes;
}

// delete the items selected in the list
let bulkDeleteInProgress = false;
async function deleteSelectedVolumes() {
Expand All @@ -123,10 +110,6 @@ async function deleteSelectedVolumes() {
}
}

function openDetailsVolume(volume: VolumeInfoUI) {
router.goto(`/volumes/${encodeURI(volume.name)}/${encodeURI(volume.engineId)}/summary`);
}

let refreshTimeouts: NodeJS.Timeout[] = [];
const SECOND = 1000;
function refreshAge() {
Expand Down Expand Up @@ -185,6 +168,52 @@ async function fetchUsageData() {
function gotoCreateVolume(): void {
router.goto('/volumes/create');
}

let selectedItemsNumber: number;
let table: Table;

let statusColumn = new Column<VolumeInfoUI>('Status', {
align: 'center',
width: '70px',
renderer: VolumeColumnStatus,
comparator: (a, b) => Number(b.inUse) - Number(a.inUse),
});

let nameColumn = new Column<VolumeInfoUI>('Name', {
width: '3fr',
renderer: VolumeColumnName,
comparator: (a, b) => a.shortName.localeCompare(b.shortName),
});

let envColumn = new Column<VolumeInfoUI>('Environment', {
renderer: VolumeColumnEnvironment,
comparator: (a, b) => a.engineName.localeCompare(b.engineName),
});

let ageColumn = new Column<VolumeInfoUI>('Age', {
renderer: VolumeColumnAge,
comparator: (a, b) => moment().diff(a.created) - moment().diff(b.created),
});

let sizeColumn = new Column<VolumeInfoUI>('Size', {
align: 'right',
renderer: VolumeColumnSize,
comparator: (a, b) => b.size - a.size,
});

const columns: Column<VolumeInfoUI>[] = [
statusColumn,
nameColumn,
envColumn,
ageColumn,
sizeColumn,
new Column<VolumeInfoUI>('Actions', { align: 'right', renderer: VolumeColumnActions }),
];

const row = new Row<VolumeInfoUI>({
selectable: volume => !volume.inUse,
disabledText: 'Volume is used by a container',
});
</script>

<NavPage bind:searchTerm="{searchTerm}" title="volumes">
Expand Down Expand Up @@ -215,76 +244,14 @@ function gotoCreateVolume(): void {
</div>

<div class="flex min-w-full h-full" slot="content">
<table class="mx-5 w-full h-fit" class:hidden="{volumes.length === 0}">
<!-- title -->
<thead class="sticky top-0 bg-charcoal-700 z-[2]">
<tr class="h-7 uppercase text-xs text-gray-600">
<th class="whitespace-nowrap w-5"></th>
<th class="px-2 w-5">
<Checkbox
title="Toggle all"
bind:checked="{selectedAllCheckboxes}"
indeterminate="{selectedItemsNumber > 0 && !selectedAllCheckboxes}"
on:click="{event => toggleAllVolumes(event.detail)}" />
</th>
<th class="text-center font-extrabold w-10 px-2">status</th>
<th class="w-10">Name</th>
<th class="px-6 whitespace-nowrap">age</th>
<th class="px-6 whitespace-nowrap text-end">size</th>
<th class="text-right pr-2">Actions</th>
</tr>
</thead>
<tbody>
{#each volumes as volume}
<tr class="group h-12 bg-charcoal-800 hover:bg-zinc-700">
<td class="rounded-tl-lg rounded-bl-lg w-5"> </td>
<td class="px-2">
<Checkbox
title="Toggle volume"
bind:checked="{volume.selected}"
disabled="{volume.inUse}"
disabledTooltip="Volume is used by a container" />
</td>
<td class="bg-charcoal-800 group-hover:bg-zinc-700 flex flex-row justify-center h-12">
<div class="grid place-content-center ml-3 mr-4">
<StatusIcon icon="{VolumeIcon}" status="{volume.inUse ? 'USED' : 'UNUSED'}" />
</div>
</td>
<td class="whitespace-nowrap w-10 hover:cursor-pointer" on:click="{() => openDetailsVolume(volume)}">
<div class="flex items-center">
<div class="">
<div class="flex flex-row items-center">
<div class="text-sm text-gray-300">{volume.shortName}</div>
</div>
<div class="flex flex-row text-xs font-extra-light text-gray-900">
<!-- Hide in case of single engine-->
{#if multipleEngines}
<div class="px-2 inline-flex text-xs font-extralight rounded-full bg-slate-800 text-slate-400">
{volume.engineName}
</div>
{/if}
</div>
</div>
</div>
</td>
<td class="px-6 py-2 whitespace-nowrap w-10">
<div class="flex items-center">
<div class="text-sm text-gray-700">{volume.age}</div>
</div>
</td>
<td class="px-6 py-2 whitespace-nowrap w-10">
<div class="flex">
<div class="w-full text-right text-sm text-gray-700">{volume.humanSize}</div>
</div>
</td>
<td class="pl-6 text-right whitespace-nowrap rounded-tr-lg rounded-br-lg">
<VolumeActions volume="{volume}" />
</td>
</tr>
<tr><td class="leading-[8px]">&nbsp;</td></tr>
{/each}
</tbody>
</table>
<Table
kind="volume"
bind:this="{table}"
bind:selectedItemsNumber="{selectedItemsNumber}"
data="{volumes}"
columns="{columns}"
row="{row}">
</Table>

{#if providerConnections.length === 0}
<NoContainerEngineEmptyScreen />
Expand Down
1 change: 1 addition & 0 deletions packages/renderer/src/lib/volume/volume-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export class VolumeUtils {
driver: this.getDriver(volumeInfo),
created: volumeInfo.CreatedAt,
age: this.humanizeAge(volumeInfo.CreatedAt),
size: volumeInfo.UsageData ? volumeInfo.UsageData?.Size : -1,
humanSize: this.getSize(volumeInfo),
engineId: volumeInfo.engineId,
engineName: volumeInfo.engineName,
Expand Down