Skip to content

feat: display provisioner jobs and daemons for an organization #16532

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

Merged
merged 22 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b6430bb
Set base structure to display the provisioner jobs
BrunoQuaresma Feb 5, 2025
5d7d58f
Merge branch 'main' of https://github.com/coder/coder into bq/refacto…
BrunoQuaresma Feb 7, 2025
643c362
[WIP]: Load data and display them in the table
BrunoQuaresma Feb 7, 2025
f9db209
Merge branch 'main' of https://github.com/coder/coder into bq/refacto…
BrunoQuaresma Feb 10, 2025
6e967f1
Update table to use API data
BrunoQuaresma Feb 10, 2025
943b7d7
Finish job structure
BrunoQuaresma Feb 10, 2025
2bc6ccf
Display tiny alert for error
BrunoQuaresma Feb 10, 2025
71f4fe5
Fix tags
BrunoQuaresma Feb 10, 2025
f027485
Add daemons page
BrunoQuaresma Feb 10, 2025
dcf8140
Merge branch 'main' of https://github.com/coder/coder into bq/refacto…
BrunoQuaresma Feb 10, 2025
994e186
Merge branch 'main' of https://github.com/coder/coder into bq/refacto…
BrunoQuaresma Feb 11, 2025
3083cef
Merge branch 'main' of https://github.com/coder/coder into bq/refacto…
BrunoQuaresma Feb 11, 2025
d66141e
Display all daemon data from server
BrunoQuaresma Feb 11, 2025
49a7ec7
Remove unused imports
BrunoQuaresma Feb 11, 2025
ffee2ed
Run fmt
BrunoQuaresma Feb 11, 2025
7802636
Add cancel provisioner job
BrunoQuaresma Feb 12, 2025
4f9030f
Run fmt
BrunoQuaresma Feb 12, 2025
22dc7be
Merge branch 'main' of https://github.com/coder/coder into bq/refacto…
BrunoQuaresma Feb 13, 2025
0ff39e5
Merge branch 'main' of https://github.com/coder/coder into bq/refacto…
BrunoQuaresma Feb 14, 2025
5953960
Apply PR reviews
BrunoQuaresma Feb 14, 2025
aabf8df
FMT
BrunoQuaresma Feb 14, 2025
ed61ce7
Reset devcontainer.json
BrunoQuaresma Feb 18, 2025
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
Prev Previous commit
Next Next commit
Display all daemon data from server
  • Loading branch information
BrunoQuaresma committed Feb 11, 2025
commit d66141e74b7d3404466e2754b7fb2cce22534f11
1 change: 1 addition & 0 deletions site/src/api/queries/organizations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
AuthorizationResponse,
CreateOrganizationRequest,
GroupSyncSettings,
ProvisionerDaemon,
RoleSyncSettings,
UpdateOrganizationRequest,
} from "api/typesGenerated";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { FC, HTMLProps } from "react";
import { cn } from "utils/cn";

export const DataGrid: FC<HTMLProps<HTMLDivElement>> = ({
className,
...props
}) => {
return (
<div
{...props}
className={cn([
"grid grid-cols-[auto_1fr] gap-x-4 items-center",
"[&_span:nth-of-type(even)]:text-content-primary [&_span:nth-of-type(even)]:font-mono",
"[&_span:nth-of-type(even)]:leading-[22px]",
className,
])}
/>
);
};

export const DataGridSpace: FC<HTMLProps<HTMLDivElement>> = ({
className,
...props
}) => {
return (
<div aria-hidden {...props} className={cn(["h-6 col-span-2", className])} />
);
};
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { provisionerDaemons } from "api/queries/organizations";
import type {
Organization,
ProvisionerDaemon,
ProvisionerDaemonStatus,
} from "api/typesGenerated";
import { Badge } from "components/Badge/Badge";
import type { Organization, ProvisionerDaemon } from "api/typesGenerated";
import { Link } from "components/Link/Link";
import {
StatusIndicator,
Expand All @@ -20,14 +15,18 @@ import {
TableRow,
} from "components/Table/Table";
import { TableEmpty } from "components/TableEmpty/TableEmpty";
import { TableLoader } from "components/TableLoader/TableLoader";
import { ChevronDownIcon, ChevronRightIcon } from "lucide-react";
import { useState, type FC } from "react";
import { useQuery } from "react-query";
import { cn } from "utils/cn";
import { docs } from "utils/docs";
import { relativeTime } from "utils/time";
import { JobStatusIndicator } from "./JobStatusIndicator";
import { Avatar } from "components/Avatar/Avatar";
import { DataGrid, DataGridSpace } from "./DataGrid";
import { ShrinkTags, Tag, Tags } from "./Tags";
import { Loader } from "components/Loader/Loader";
import { EmptyState } from "components/EmptyState/EmptyState";

type ProvisionerDaemonsPageProps = {
org: Organization;
Expand All @@ -36,9 +35,18 @@ type ProvisionerDaemonsPageProps = {
export const ProvisionerDaemonsPage: FC<ProvisionerDaemonsPageProps> = ({
org,
}) => {
const { data: daemons, isLoadingError } = useQuery(
provisionerDaemons(org.id),
);
const { data: daemons, isLoadingError } = useQuery({
...provisionerDaemons(org.id),
select: (data) =>
data.toSorted((a, b) => {
if (!a.last_seen_at) return 1;
if (!b.last_seen_at) return -1;
return (
new Date(b.last_seen_at).getTime() -
new Date(a.last_seen_at).getTime()
);
}),
});

return (
<section className="flex flex-col gap-8">
Expand Down Expand Up @@ -69,12 +77,24 @@ export const ProvisionerDaemonsPage: FC<ProvisionerDaemonsPageProps> = ({
daemons.length > 0 ? (
daemons.map((d) => <DaemonRow key={d.id} daemon={d} />)
) : (
<TableEmpty message="No provisioner daemons found" />
<TableRow>
<TableCell colSpan={999}>
<EmptyState message="No provisioner daemons found" />
</TableCell>
</TableRow>
)
) : isLoadingError ? (
<TableEmpty message="Error loading the provisioner daemons" />
<TableRow>
<TableCell colSpan={999}>
<EmptyState message="Error loading the provisioner daemons" />
</TableCell>
</TableRow>
) : (
<TableLoader />
<TableRow>
<TableCell colSpan={999}>
<Loader />
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
Expand Down Expand Up @@ -116,39 +136,46 @@ const DaemonRow: FC<DaemonRowProps> = ({ daemon }) => {
</span>
</button>
</TableCell>
<TableCell>{daemon.name}</TableCell>
<TableCell>Template</TableCell>
<TableCell>
<div className="flex items-center gap-1 flex-wrap">
{Object.entries(daemon.tags).map(([k, v]) => (
<Badge size="sm" key={k} className="whitespace-nowrap">
[{k}
{v && `=${v}`}]
</Badge>
))}
</div>
<span className="block whitespace-nowrap text-ellipsis overflow-hidden">
{daemon.name}
</span>
</TableCell>
<TableCell>
<StatusIndicator
size="sm"
variant={statusIndicatorVariant(daemon.status)}
>
{daemon.current_job ? (
<div className="flex items-center gap-1 whitespace-nowrap">
<Avatar
variant="icon"
src={daemon.current_job.template_icon}
fallback={
daemon.current_job.template_display_name ||
daemon.current_job.template_name
}
/>
{daemon.current_job.template_display_name ??
daemon.current_job.template_name}
</div>
) : (
<span className="whitespace-nowrap">Not linked</span>
)}
</TableCell>
<TableCell>
<ShrinkTags tags={daemon.tags} />
</TableCell>
<TableCell>
<StatusIndicator size="sm" variant={statusIndicatorVariant(daemon)}>
<StatusIndicatorDot />
<span className="[&:first-letter]:uppercase">{daemon.status}</span>
<span className="[&:first-letter]:uppercase">
{statusLabel(daemon)}
</span>
</StatusIndicator>
</TableCell>
</TableRow>

{isOpen && (
<TableRow>
<TableCell colSpan={999} className="p-4 border-t-0">
<div
className={cn([
"grid grid-cols-[auto_1fr] gap-x-4 items-center",
"[&_span:nth-child(even)]:text-content-primary [&_span:nth-child(even)]:font-mono",
"[&_span:nth-child(even)]:leading-[22px]",
])}
>
<DataGrid>
<span>Last seen:</span>
<span>{daemon.last_seen_at}</span>

Expand All @@ -158,8 +185,19 @@ const DaemonRow: FC<DaemonRowProps> = ({ daemon }) => {
<span>Version:</span>
<span>{daemon.version}</span>

<span>Tags:</span>
<span>
<Tags>
{Object.entries(daemon.tags).map(([key, value]) => (
<Tag key={key} label={key} value={value} />
))}
</Tags>
</span>

{daemon.current_job && (
<>
<DataGridSpace />

<span>Last job:</span>
<span>{daemon.current_job.id}</span>

Expand All @@ -172,6 +210,8 @@ const DaemonRow: FC<DaemonRowProps> = ({ daemon }) => {

{daemon.previous_job && (
<>
<DataGridSpace />

<span>Previous job:</span>
<span>{daemon.previous_job.id}</span>

Expand All @@ -181,7 +221,7 @@ const DaemonRow: FC<DaemonRowProps> = ({ daemon }) => {
</span>
</>
)}
</div>
</DataGrid>
</TableCell>
</TableRow>
)}
Expand All @@ -190,9 +230,13 @@ const DaemonRow: FC<DaemonRowProps> = ({ daemon }) => {
};

function statusIndicatorVariant(
status: ProvisionerDaemonStatus | null,
daemon: ProvisionerDaemon,
): StatusIndicatorProps["variant"] {
switch (status) {
if (daemon.previous_job && daemon.previous_job.status === "failed") {
return "failed";
}

switch (daemon.status) {
case "idle":
return "success";
case "busy":
Expand All @@ -202,3 +246,20 @@ function statusIndicatorVariant(
return "inactive";
}
}

function statusLabel(daemon: ProvisionerDaemon) {
if (daemon.previous_job && daemon.previous_job.status === "failed") {
return "Last job failed";
}

switch (daemon.status) {
case "idle":
return "Idle";
case "busy":
return "Busy...";
case "offline":
return "Disconnected";
case null:
return "Unknown";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
TableRow,
} from "components/Table/Table";
import { TableEmpty } from "components/TableEmpty/TableEmpty";
import { TableLoader } from "components/TableLoader/TableLoader";
import {
Tooltip,
TooltipContent,
Expand All @@ -32,6 +31,10 @@ import { cn } from "utils/cn";
import { docs } from "utils/docs";
import { relativeTime } from "utils/time";
import { JobStatusIndicator } from "./JobStatusIndicator";
import { DataGrid } from "./DataGrid";
import { ShrinkTags, Tag, Tags } from "./Tags";
import { Loader } from "components/Loader/Loader";
import { EmptyState } from "components/EmptyState/EmptyState";

type ProvisionerJobsPageProps = {
org: Organization;
Expand Down Expand Up @@ -64,12 +67,24 @@ export const ProvisionerJobsPage: FC<ProvisionerJobsPageProps> = ({ org }) => {
jobs.length > 0 ? (
jobs.map((j) => <JobRow key={j.id} job={j} />)
) : (
<TableEmpty message="No provisioner jobs found" />
<TableRow>
<TableCell colSpan={999}>
<EmptyState message="No provisioner jobs found" />
</TableCell>
</TableRow>
)
) : isLoadingError ? (
<TableEmpty message="Error loading the provisioner jobs" />
<TableRow>
<TableCell colSpan={999}>
<EmptyState message="Error loading the provisioner jobs" />
</TableCell>
</TableRow>
) : (
<TableLoader />
<TableRow>
<TableCell colSpan={999}>
<Loader />
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
Expand Down Expand Up @@ -127,18 +142,11 @@ const JobRow: FC<JobRowProps> = ({ job }) => {
{metadata.template_display_name ?? metadata.template_name}
</div>
) : (
"Not linked to any template"
<span className="whitespace-nowrap">Not linked</span>
)}
</TableCell>
<TableCell>
<div className="flex items-center gap-1 flex-wrap">
{Object.entries(job.tags).map(([k, v]) => (
<Badge size="sm" key={k} className="whitespace-nowrap">
[{k}
{v && `=${v}`}]
</Badge>
))}
</div>
<ShrinkTags tags={job.tags} />
</TableCell>
<TableCell>
<JobStatusIndicator job={job} />
Expand Down Expand Up @@ -176,13 +184,7 @@ const JobRow: FC<JobRowProps> = ({ job }) => {
<span className="[&:first-letter]:uppercase">{job.error}</span>
</div>
)}
<div
className={cn([
"grid grid-cols-[auto_1fr] gap-x-4 items-center",
"[&_span:nth-child(even)]:text-content-primary [&_span:nth-child(even)]:font-mono",
"[&_span:nth-child(even)]:leading-[22px]",
])}
>
<DataGrid>
<span>Job ID:</span>
<span>{job.id}</span>

Expand All @@ -206,7 +208,16 @@ const JobRow: FC<JobRowProps> = ({ job }) => {
<span>
{job.queue_position}/{job.queue_size}
</span>
</div>

<span>Tags:</span>
<span>
<Tags>
{Object.entries(job.tags).map(([key, value]) => (
<Tag key={key} label={key} value={value} />
))}
</Tags>
</span>
</DataGrid>
</TableCell>
</TableRow>
)}
Expand Down
Loading