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: add messages view #7

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
4 changes: 4 additions & 0 deletions src/lib/api/ApiSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ type Topics =
method: 'GET';
path: `/streams/${number}/topics/${number}`;
}
| {
method: 'GET';
path: `/streams/${number}/topics/${number}/messages`;
}
| {
method: 'POST';
path: `/streams/${number}/topics`;
Expand Down
28 changes: 15 additions & 13 deletions src/lib/components/Modals/AppModals.svelte
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
<script lang="ts" context="module">
import type { ComponentProps, ComponentType, SvelteComponent } from 'svelte';
import { fade } from 'svelte/transition';
import { writable } from 'svelte/store';
import AddPartitionsModal from './AddPartitionsModal.svelte';
import AddStreamModal from './AddStreamModal.svelte';
import AddTopicModal from './AddTopicModal.svelte';
import StreamSettingsModal from './StreamSettingsModal.svelte';
import TopicSettingsModal from './TopicSettingsModal.svelte';
import AddPartitionsModal from './AddPartitionsModal.svelte';
import DeletePartitionsModal from './DeletePartitionsModal.svelte';
import AddUserModal from './AddUserModal.svelte';
import DeletePartitionsModal from './DeletePartitionsModal.svelte';
import DeleteUserModal from './DeleteUserModal.svelte';
import EditUserModal from './EditUserModal.svelte';
import EditUserPermissionsModal from './EditUserPermissionsModal.svelte';
import InspectMessage from './InspectMessage.svelte';
import StreamSettingsModal from './StreamSettingsModal.svelte';
import TopicSettingsModal from './TopicSettingsModal.svelte';
import { fade } from 'svelte/transition';
import { noTypeCheck } from '$lib/utils/noTypeCheck';
import { writable } from 'svelte/store';

const modals = {
AddTopicModal,
AddStreamModal,
StreamSettingsModal,
TopicSettingsModal,
AddPartitionsModal,
DeletePartitionsModal,
AddStreamModal,
AddTopicModal,
AddUserModal,
DeletePartitionsModal,
DeleteUserModal,
EditUserModal,
EditUserPermissionsModal
EditUserPermissionsModal,
InspectMessage,
StreamSettingsModal,
TopicSettingsModal,
};

type DistributiveOmit<T, K extends string> = T extends T ? Omit<T, K> : never;
Expand Down Expand Up @@ -73,7 +75,7 @@
/>

{#if $openedModal}
<div transition:fade={{ duration: 100 }} class="fixed inset-0 bg-black/40 z-[500]" />
<div transition:fade={{ duration: 100 }} class="fixed inset-0 bg-black/40 z-[500]" on:click={$openedModal.props.closeModal} role="button" tabindex={1} />
<svelte:component
this={noTypeCheck(modals[$openedModal.modal])}
{...$openedModal.props}
Expand Down
58 changes: 58 additions & 0 deletions src/lib/components/Modals/InspectMessage.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<script lang="ts">
import { setError, superForm, superValidateSync } from 'sveltekit-superforms/client';
import { z } from 'zod';
import Input from '../Input.svelte';
import Select from '../Select.svelte';
import Button from '../Button.svelte';
import ModalBase from './ModalBase.svelte';
import type { CloseModalFn } from '$lib/types/utilTypes';
import type { StreamDetails } from '$lib/domain/StreamDetails';
import { fetchRouteApi } from '$lib/api/fetchRouteApi';
import { intervalToDuration } from 'date-fns';
import { durationFormatter } from '$lib/utils/formatters/durationFormatter';
import { numberSizes } from '$lib/utils/constants/numberSizes';
import { dataHas } from '$lib/utils/dataHas';
import { invalidateAll } from '$app/navigation';
import { showToast } from '../AppToasts.svelte';
import { customInvalidateAll } from '../PeriodicInvalidator.svelte';
import { type Message } from '../../domain/Message';

export let closeModal: CloseModalFn;
export let message: Message;
</script>

<ModalBase {closeModal} title="Message details">
<div class="flex flex-col justify-start items-center h-[650px] w-[1000px] gap-4">
<div class="grid grid-cols-[1fr,1fr,1fr] gap-x-1 mt-16">
<p class="ml-1 text-sm text-color">
{message.id}
</p>
<p></p>
<p class="ml-1 text-sm text-color">
{message.state}
</p>
</div>
<div class="grid grid-cols-[1fr,1fr,1fr] gap-x-1 mt-16">
<p class="ml-1 text-sm text-color">
{message.checksum}
</p>
<p></p>
<p class="ml-1 text-sm text-color">
{message.header}
</p>
</div>
<div class="grid grid-cols-[1fr,1fr,1fr] gap-x-1 mt-16">
<p class="ml-1 text-sm text-color">
{message.timestamp}
</p>
<p></p>
<p class="ml-1 text-sm text-color">
{message.header}
</p>
</div>

<textarea cols="60" rows="10">
{message.payload}
</textarea>
</div>
</ModalBase>
6 changes: 3 additions & 3 deletions src/lib/components/Modals/ModalBase.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@
)}
>
<div class="h-[15%]">
<Button variant="rounded" on:click={() => closeModal()} class="absolute top-5 right-5 p-2">
<Button variant="rounded" on:click={() => closeModal()} class="absolute p-2 top-5 right-5">
<Icon name="close" strokeWidth={2.3} />
</Button>

{#if title}
<div class="flex gap-2 items-center">
<h2 class="text-xl text-color font-semibold mb-7">{title}</h2>
<div class="flex items-center gap-2">
<h2 class="text-xl font-semibold text-color mb-7">{title}</h2>
<slot name="titleSuffix" />
</div>
{/if}
Expand Down
6 changes: 3 additions & 3 deletions src/lib/components/RouteComponents/Settings/UsersTab.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
/>
{/if}
</div>
<div class=" px-5 font-semibold">
<div class="px-5 font-semibold ">
{row.id}
</div>

Expand All @@ -126,7 +126,7 @@
</span>
</div>

<div class=" whitespace-nowrap px-5">
<div class="px-5 whitespace-nowrap">
{row.createdAt}
</div>

Expand Down Expand Up @@ -154,7 +154,7 @@
close();
}}
class={twMerge(
' grid grid-cols-[20px,1fr] gap-x-1 rounded-md items-center w-full px-2 py-2 text-sm text-color cursor-default hoverable-strong'
'grid grid-cols-[20px,1fr] gap-x-1 rounded-md items-center w-full px-2 py-2 text-sm text-color cursor-default hoverable-strong'
)}
>
<span>
Expand Down
13 changes: 9 additions & 4 deletions src/lib/components/SortableList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
});

export let hrefBuilder: ((item: T) => string) | undefined = undefined;
export let onclickAction: ((index: number) => any) | undefined = undefined;

let ordering: Ordering<T> = {
key: undefined,
Expand Down Expand Up @@ -78,7 +79,7 @@
}
</script>

<div class="overflow-auto flex-1">
<div class="flex-1 overflow-auto">
<!-- header -->
<div
class={twMerge(
Expand All @@ -94,7 +95,7 @@
key: columnName,
asc: ordering.key !== columnName ? true : !ordering.asc
})}
class="flex items-center px-5 hover:cursor-pointer hover:bg-shadeL400 dark:hover:bg-shadeD200 dark:text-white justify-between outline-none focus:outline-none focus-visible:ring ring-inset ring-blue-600/60 transition-colors"
class="flex items-center justify-between px-5 transition-colors outline-none hover:cursor-pointer hover:bg-shadeL400 dark:hover:bg-shadeD200 dark:text-white focus:outline-none focus-visible:ring ring-inset ring-blue-600/60"
>
<span>
{columnDisplayedName}
Expand Down Expand Up @@ -126,15 +127,19 @@
<!-- body -->
<div class="min-w-[1300px] h-[calc(100%-60px)] pb-3 overflow-auto" bind:this={bodyElem}>
{#if data.length === 0 && !isAnimating}
<div class="flex items-center justify-center text-gray-400 mt-14 text-lg">
<div class="flex items-center justify-center text-lg text-gray-400 mt-14">
<em>{emptyDataMessage}</em>
</div>
{/if}

{#each orderedData as item (item.id)}
{#each orderedData as item, index}
<svelte:element
this={hrefBuilder ? 'a' : 'div'}
href={hrefBuilder && hrefBuilder(item)}
role="button"
tabindex={index}
aria-roledescription="Display message details"
on:click={onclickAction && onclickAction(index)}
on:introstart={noTypeCheck((e) => {
if (!animationEnabled) return;
e.target.style.backgroundColor = 'rgb(74, 222, 128)';
Expand Down
36 changes: 36 additions & 0 deletions src/lib/domain/Message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export type Message = {
checksum: number;
header: Record<string, string>;
id: number;
offset: number;
payload: string;
truncatedPayload: string
state: "available";
timestamp: number;
};

export function messageMapper(item: any): Message {
let payload = "";

try {
payload = atob(item.payload)
} catch {
payload = "[NOT DECODABLE]"
}

let truncatedPayload = payload;
if (payload.length > 100) {
truncatedPayload = `${payload.slice(0, 100)} [...]`
}

return {
id: item.id,
header: item.header,
offset: item.offset,
payload,
state: item.state,
timestamp: item.timestamp,
checksum: item.checksum,
truncatedPayload,
};
}
11 changes: 11 additions & 0 deletions src/lib/domain/MessageDetails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Message, messageMapper } from './Message';

type Payload = {
partition_id: number,
current_offset: number,
messages: Array<Message>,
}

export function messageDetailsMapper(payload: Payload): Array<Message> {
return payload.messages.map(messageMapper);
}
1 change: 1 addition & 0 deletions src/lib/types/appRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type DashboardRoutes = `/dashboard/${
| 'streams'
| `streams/${number}`
| `streams/${number}/topics/${number}`
| `streams/${number}/topics/${number}/messages`
| 'clients'
| 'logs'
| SettingsSegment}`;
Expand Down
3 changes: 0 additions & 3 deletions src/routes/dashboard/streams/[streamId=i32]/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
<script lang="ts">
import { invalidateAll } from '$app/navigation';
import { page } from '$app/stores';
import Button from '$lib/components/Button.svelte';
import Icon from '$lib/components/Icon.svelte';
import { openModal } from '$lib/components/Modals/AppModals.svelte';
import SortableList from '$lib/components/SortableList.svelte';
import { typedRoute } from '$lib/types/appRoutes.js';
import { arrayMax } from '$lib/utils/arrayMax';
import { bytesFormatter } from '$lib/utils/formatters/bytesFormatter';
import { onMount } from 'svelte';

export let data;
$: stream = data.streamDetails;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import Button from '$lib/components/Button.svelte';
import Icon from '$lib/components/Icon.svelte';
import { goto } from '$app/navigation';
import { typedRoute } from '$lib/types/appRoutes.js';
import { openModal } from '$lib/components/Modals/AppModals.svelte';

import SortableList from '$lib/components/SortableList.svelte';
Expand All @@ -18,7 +19,7 @@
<Icon name="arrowLeft" class="h-[40px] w-[30px]" />
</Button>

<h1 class="font-semibold text-xl text-color">Topic {topic.name}</h1>
<h1 class="text-xl font-semibold text-color">Topic {topic.name}</h1>

<Button
variant="rounded"
Expand Down Expand Up @@ -60,6 +61,8 @@
emptyDataMessage="No partitions found."
rowClass="grid grid-cols-[150px_1fr_1fr_1fr_1fr_1fr]"
data={topic.partitions}
hrefBuilder={() =>
typedRoute(`/dashboard/streams/${+$page.params.streamId}/topics/${topic.id}/messages`)}
colNames={{
id: 'ID',
currentOffset: 'Offset',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { fetchApi } from '$lib/api/fetchApi';
import { handleFetchErrors } from '$lib/api/handleFetchErrors';
import { messageDetailsMapper } from '../../../../../../../lib/domain/MessageDetails';
import { topicDetailsMapper } from '../../../../../../../lib/domain/TopicDetails';

export const load = async ({ params, cookies }) => {
const getMessages = async () => {
const result = await fetchApi({
method: 'GET',
path: `/streams/${+params.streamId}/topics/${+params.topicId}/messages`,
cookies,
queryParams: {
count: "1000",
}
});

const { data } = await handleFetchErrors(result, cookies);

return messageDetailsMapper(data as any);
};

const getTopic = async () => {
const result = await fetchApi({
method: 'GET',
path: `/streams/${+params.streamId}/topics/${+params.topicId}`,
cookies
});

const { data } = await handleFetchErrors(result, cookies);

return topicDetailsMapper(data);
};

return {
messages: await getMessages(),
topic: await getTopic(),
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<script lang="ts">
import { page } from '$app/stores';
import Button from '$lib/components/Button.svelte';
import Icon from '$lib/components/Icon.svelte';
import { goto } from '$app/navigation';
import { typedRoute } from '$lib/types/appRoutes.js';
import { openModal } from '$lib/components/Modals/AppModals.svelte';

import SortableList from '$lib/components/SortableList.svelte';

export let data;
$: messages = data.messages;
$: topic = data.topic;
$: prevPage = $page.url.pathname.split('/').slice(0, 6).join('/') + '/';
</script>

<div class="h-[80px] flex text-xs items-center pl-2 pr-5">
<Button variant="rounded" class="mr-5" on:click={() => goto(prevPage)}>
<Icon name="arrowLeft" class="h-[40px] w-[30px]" />
</Button>

<h1 class="text-xl font-semibold text-color">Messages for {topic.name}</h1>

<div class="flex gap-3 ml-7">
<div class="chip">
<span>Messages: {topic.messagesCount}</span>
</div>
</div>
</div>

<SortableList
emptyDataMessage="No messages found."
rowClass="grid grid-cols-[110px_2fr_1fr_1fr_1fr_1fr]"
data={data.messages}
onclickAction={(index) =>
openModal('InspectMessage', { message: data.messages[index] })
}
colNames={{
offset: "Offset",
truncatedPayload: "Payload",
timestamp: "timestamp",
checksum: "Checksum",
header: "Headers",
}}
/>