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

feat: navigation and dashboard #934

Merged
merged 1 commit into from
Oct 17, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions packages/frontend/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import Route from './lib/Route.svelte';
import Build from './Build.svelte';
import { onMount } from 'svelte';
import { getRouterState } from './api/client';
import Homepage from './Homepage.svelte';
import { rpcBrowser } from '/@/api/client';
import { Messages } from '/@shared/src/messages/Messages';
import DiskImageDetails from './lib/disk-image/DiskImageDetails.svelte';
import Navigation from './Navigation.svelte';
import DiskImagesList from './lib/disk-image/DiskImagesList.svelte';
import Dashboard from './lib/dashboard/Dashboard.svelte';

router.mode.hash();

Expand All @@ -22,24 +24,30 @@ onMount(() => {
isMounted = true;

return rpcBrowser.subscribe(Messages.MSG_NAVIGATE_BUILD, (x: string) => {
router.goto(`/build/${x}`);
router.goto(`/disk-images/build/${x}`);
});
});
</script>

<Route path="/*" breadcrumb="Bootable Containers" isAppMounted={isMounted} let:meta>
<main class="flex flex-col w-screen h-screen overflow-hidden bg-[var(--pd-content-bg)]">
<div class="flex flex-row w-full h-full overflow-hidden">
<Route path="/" breadcrumb="Bootable Containers">
<Homepage />
<Navigation meta={meta} />

<Route path="/" breadcrumb="Dashboard">
<Dashboard />
</Route>
<Route path="/build" breadcrumb="Build">
<Build />
<Route path="/disk-images/" breadcrumb="Disk Images">
<DiskImagesList />
</Route>
<Route path="/details/:id/*" breadcrumb="Disk Image Details" let:meta>
<Route path="/disk-image/:id/*" breadcrumb="Disk Image Details" let:meta>
<DiskImageDetails id={meta.params.id} />
</Route>
<Route path="/build/:name/:tag" breadcrumb="Build" let:meta>

<Route path="/disk-images/build" breadcrumb="Build">
<Build />
</Route>
<Route path="/disk-images/build/:name/:tag" breadcrumb="Build" let:meta>
<Build imageName={decodeURIComponent(meta.params.name)} imageTag={decodeURIComponent(meta.params.tag)} />
</Route>
</div>
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/Build.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -830,5 +830,5 @@ test('confirm successful build goes to logs', async () => {
// check that clicking redirects to the build logs page
expect(router.goto).not.toHaveBeenCalled();
await userEvent.click(build);
expect(router.goto).toHaveBeenCalledWith(`/details/bmFtZTE=/build`);
expect(router.goto).toHaveBeenCalledWith(`/disk-image/bmFtZTE=/build`);
});
15 changes: 6 additions & 9 deletions packages/frontend/src/Build.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import DiskImageIcon from './lib/DiskImageIcon.svelte';
import { Button, Input, EmptyScreen, FormPage, Checkbox, ErrorMessage } from '@podman-desktop/ui-svelte';
import Link from './lib/Link.svelte';
import { historyInfo } from '/@/stores/historyInfo';
import { goToDiskImages } from './lib/navigation';

export let imageName: string | undefined = undefined;
export let imageTag: string | undefined = undefined;
Expand Down Expand Up @@ -262,7 +263,7 @@ async function buildBootcImage() {
const found = $historyInfo.find(info => info.id === buildID);

if (found) {
router.goto(`/details/${btoa(found.id)}/build`);
router.goto(`/disk-image/${btoa(found.id)}/build`);
break; // Exit the loop if the build is found
}

Expand Down Expand Up @@ -422,20 +423,16 @@ $: if (availableArchitectures) {
buildArch = undefined;
}
}

export function goToHomePage(): void {
router.goto('/');
}
</script>

<FormPage
title="Build Disk Image"
inProgress={buildInProgress}
breadcrumbLeftPart="Bootable Containers"
breadcrumbLeftPart="Disk Images"
breadcrumbRightPart="Build Disk Image"
breadcrumbTitle="Go back to homepage"
onclose={goToHomePage}
onbreadcrumbClick={goToHomePage}>
breadcrumbTitle="Go back to disk images"
onclose={goToDiskImages}
onbreadcrumbClick={goToDiskImages}>
<DiskImageIcon slot="icon" size="30px" />

<div slot="content" class="p-5 min-w-full h-fit">
Expand Down
20 changes: 20 additions & 0 deletions packages/frontend/src/Navigation.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<script lang="ts">
import type { TinroRouteMeta } from 'tinro';
import { SettingsNavItem } from '@podman-desktop/ui-svelte';

export let meta: TinroRouteMeta;
</script>

<nav
class="z-1 w-leftsidebar min-w-leftsidebar shadow flex-col justify-between flex bg-[var(--pd-secondary-nav-bg)] border-[var(--pd-global-nav-bg-border)] border-r-[1px]"
aria-label="Navigation">
<div class="flex items-center">
<a href="/" title="Navigate to dashboard" class="pt-4 pl-3 px-5 mb-10 flex items-center ml-[4px]">
<p class="text-xl font-semibold text-[color:var(--pd-secondary-nav-header-text)]">Bootable Containers</p>
</a>
</div>
<div class="h-full overflow-hidden hover:overflow-y-auto" style="margin-bottom:auto">
<SettingsNavItem title="Dashboard" selected={meta.url === '/'} href="/" />
<SettingsNavItem title="Disk Images" selected={meta.url.startsWith('/disk-image')} href="/disk-images" />
</div>
</nav>
2 changes: 1 addition & 1 deletion packages/frontend/src/lib/BootcActions.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ async function deleteBuild(): Promise<void> {

// Navigate to the build
async function gotoLogs(): Promise<void> {
router.goto(`/details/${btoa(object.id)}/build`);
router.goto(`/disk-image/${btoa(object.id)}/build`);
}
</script>

Expand Down
115 changes: 0 additions & 115 deletions packages/frontend/src/lib/BootcEmptyScreen.svelte

This file was deleted.

2 changes: 1 addition & 1 deletion packages/frontend/src/lib/BootcImageColumn.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { BootcBuildInfo } from '/@shared/src/models/bootc';
export let object: BootcBuildInfo;

function openDetails() {
router.goto(`/details/${btoa(object.id)}/summary`);
router.goto(`/disk-image/${btoa(object.id)}/summary`);
}
</script>

Expand Down
9 changes: 0 additions & 9 deletions packages/frontend/src/lib/NoBootcImagesEmptyScreen.svelte

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import '@testing-library/jest-dom/vitest';

import { render, screen } from '@testing-library/svelte';
import { expect, test, vi } from 'vitest';
import { bootcClient } from '../api/client';
import BootcEmptyScreen from './BootcEmptyScreen.svelte';
import { bootcClient } from '../../api/client';
import Dashboard from './Dashboard.svelte';
import type { ImageInfo } from '@podman-desktop/api';

const exampleTestImage = `quay.io/bootc-extension/httpd:latest`;
Expand All @@ -45,7 +45,7 @@ const mockBootcImages: ImageInfo[] = [
},
];

vi.mock('../api/client', async () => {
vi.mock('../../api/client', async () => {
return {
bootcClient: {
listHistoryInfo: vi.fn(),
Expand All @@ -62,10 +62,10 @@ vi.mock('../api/client', async () => {
};
});

test('Expect welcome screen header on empty build page', async () => {
test('Expect basic dashboard', async () => {
vi.mocked(bootcClient.listHistoryInfo).mockResolvedValue([]);
vi.mocked(bootcClient.listBootcImages).mockResolvedValue([]);
render(BootcEmptyScreen);
render(Dashboard);

const noDeployments = screen.getByRole('heading', { name: 'Welcome to Bootable Containers' });
expect(noDeployments).toBeInTheDocument();
Expand All @@ -74,12 +74,14 @@ test('Expect welcome screen header on empty build page', async () => {
test('Expect build image button if example image does not exist', async () => {
vi.mocked(bootcClient.listHistoryInfo).mockResolvedValue([]);
vi.mocked(bootcClient.listBootcImages).mockResolvedValue(mockBootcImages);
render(BootcEmptyScreen);
render(Dashboard);

// Wait until the "Pull image" button DISSAPEARS
while (screen.queryAllByRole('button', { name: 'Pull image' }).length === 1) {
await new Promise(resolve => setTimeout(resolve, 100));
}
// Wait until the "Pull image" button disapears
await vi.waitFor(() => {
if (screen.queryAllByRole('button', { name: 'Pull image' }).length === 1) {
throw new Error();
}
});

// Build image exists since there is the example image in our mocked mockBootcImages
const buildImage = screen.getByRole('button', { name: 'Build image' });
Expand All @@ -89,12 +91,14 @@ test('Expect build image button if example image does not exist', async () => {
test('Expect pull image button if example image does not exist', async () => {
vi.mocked(bootcClient.listHistoryInfo).mockResolvedValue([]);
vi.mocked(bootcClient.listBootcImages).mockResolvedValue([]);
render(BootcEmptyScreen);
render(Dashboard);

// Wait until the "Build image" button disappears
while (screen.queryAllByRole('button', { name: 'Build image' }).length === 1) {
await new Promise(resolve => setTimeout(resolve, 100));
}
await vi.waitFor(() => {
if (screen.queryAllByRole('button', { name: 'Build image' }).length === 1) {
throw new Error();
}
});

// Pull image exists since there is no image in our mocked mockBootcImages
const pullImage = screen.getByRole('button', { name: 'Pull image' });
Expand All @@ -104,25 +108,9 @@ test('Expect pull image button if example image does not exist', async () => {
test('Clicking on Pull image button should call bootcClient.pullImage', async () => {
vi.mocked(bootcClient.listHistoryInfo).mockResolvedValue([]);
vi.mocked(bootcClient.listBootcImages).mockResolvedValue([]);
render(BootcEmptyScreen);
render(Dashboard);

const pullImage = screen.getByRole('button', { name: 'Pull image' });
pullImage.click();
expect(bootcClient.pullImage).toHaveBeenCalled();
});

test('Clicking on Build image button should navigate to the build page', async () => {
vi.mocked(bootcClient.listHistoryInfo).mockResolvedValue([]);
vi.mocked(bootcClient.listBootcImages).mockResolvedValue(mockBootcImages);
render(BootcEmptyScreen);

// Wait until the "Pull image" button disappears
while (screen.queryAllByRole('button', { name: 'Pull image' }).length === 1) {
await new Promise(resolve => setTimeout(resolve, 100));
}

const buildImage = screen.getByRole('button', { name: 'Build image' });
buildImage.click();
const [image, tag] = exampleTestImage.split(':');
expect(window.location.href).toContain(`/build/${encodeURIComponent(image)}/${encodeURIComponent(tag)}`);
});
Loading