Skip to content

Commit 643c362

Browse files
committed
[WIP]: Load data and display them in the table
1 parent 5d7d58f commit 643c362

File tree

9 files changed

+187
-407
lines changed

9 files changed

+187
-407
lines changed

.devcontainer/devcontainer.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,14 @@
99
}
1010
},
1111
// SYS_PTRACE to enable go debugging
12-
"runArgs": ["--cap-add=SYS_PTRACE"]
12+
"runArgs": [
13+
"--cap-add=SYS_PTRACE"
14+
],
15+
"customizations": {
16+
"vscode": {
17+
"extensions": [
18+
"biomejs.biome"
19+
]
20+
}
21+
}
1322
}

site/src/api/api.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2295,6 +2295,13 @@ class ApiMethods {
22952295
);
22962296
return res.data;
22972297
};
2298+
2299+
getProvisionerJobs = async (orgId: string) => {
2300+
const res = await this.axios.get<TypesGen.ProvisionerJob[]>(
2301+
`/api/v2/organizations/${orgId}/provisionerjobs`,
2302+
);
2303+
return res.data;
2304+
};
22982305
}
22992306

23002307
// This is a hard coded CSRF token/cookie pair for local development. In prod,

site/src/api/queries/organizations.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,13 @@ export const organizationPermissions = (organizationId: string | undefined) => {
244244
};
245245
};
246246

247+
export const provisionerJobs = (orgId: string) => {
248+
return {
249+
queryKey: ["organization", orgId, "provisionerjobs"],
250+
queryFn: () => API.getProvisionerJobs(orgId),
251+
};
252+
};
253+
247254
/**
248255
* Fetch permissions for all provided organizations.
249256
*

site/src/pages/DeploymentSettingsPage/ProvisionersPage/ProvisionersPage.tsx

Lines changed: 0 additions & 96 deletions
This file was deleted.
Lines changed: 152 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,52 @@
11
import { buildInfo } from "api/queries/buildInfo";
2-
import { provisionerDaemonGroups } from "api/queries/organizations";
2+
import {
3+
provisionerDaemonGroups,
4+
provisionerJobs,
5+
} from "api/queries/organizations";
6+
import type { Organization } from "api/typesGenerated";
7+
import { Avatar } from "components/Avatar/Avatar";
8+
import { Badge } from "components/Badge/Badge";
9+
import { Button } from "components/Button/Button";
310
import { EmptyState } from "components/EmptyState/EmptyState";
11+
import { Link } from "components/Link/Link";
12+
import {
13+
Table,
14+
TableBody,
15+
TableCell,
16+
TableHead,
17+
TableHeader,
18+
TableRow,
19+
} from "components/Table/Table";
20+
import { TableEmpty } from "components/TableEmpty/TableEmpty";
21+
import { TableLoader } from "components/TableLoader/TableLoader";
22+
import { TabLink, Tabs, TabsList } from "components/Tabs/Tabs";
23+
import {
24+
Tooltip,
25+
TooltipContent,
26+
TooltipProvider,
27+
TooltipTrigger,
28+
} from "components/Tooltip/Tooltip";
429
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
30+
import { BanIcon } from "lucide-react";
531
import { useDashboard } from "modules/dashboard/useDashboard";
632
import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout";
733
import type { FC } from "react";
834
import { Helmet } from "react-helmet-async";
935
import { useQuery } from "react-query";
1036
import { useParams } from "react-router-dom";
37+
import { docs } from "utils/docs";
1138
import { pageTitle } from "utils/page";
12-
import { OrganizationProvisionersPageView } from "./OrganizationProvisionersPageView";
39+
import { relativeTime } from "utils/time";
1340

1441
const OrganizationProvisionersPage: FC = () => {
15-
const { organization: organizationName } = useParams() as {
16-
organization: string;
17-
};
42+
// const { organization: organizationName } = useParams() as {
43+
// organization: string;
44+
// };
1845
const { organization } = useOrganizationSettings();
19-
const { entitlements } = useDashboard();
20-
const { metadata } = useEmbeddedMetadata();
21-
const buildInfoQuery = useQuery(buildInfo(metadata["build-info"]));
22-
const provisionersQuery = useQuery(provisionerDaemonGroups(organizationName));
46+
// const { entitlements } = useDashboard();
47+
// const { metadata } = useEmbeddedMetadata();
48+
// const buildInfoQuery = useQuery(buildInfo(metadata["build-info"]));
49+
// const provisionersQuery = useQuery(provisionerDaemonGroups(organizationName));
2350

2451
if (!organization) {
2552
return <EmptyState message="Organization not found" />;
@@ -35,14 +62,124 @@ const OrganizationProvisionersPage: FC = () => {
3562
)}
3663
</title>
3764
</Helmet>
38-
<OrganizationProvisionersPageView
39-
showPaywall={!entitlements.features.multiple_organizations.enabled}
40-
error={provisionersQuery.error}
41-
buildInfo={buildInfoQuery.data}
42-
provisioners={provisionersQuery.data}
43-
/>
65+
66+
<div className="flex flex-col gap-12">
67+
<header className="flex flex-row items-baseline justify-between">
68+
<div className="flex flex-col gap-2">
69+
<h1 className="text-3xl m-0">Provisioners</h1>
70+
</div>
71+
</header>
72+
73+
<main>
74+
<Tabs active="jobs">
75+
<TabsList>
76+
<TabLink value="jobs" to="?tab=jobs">
77+
Jobs
78+
</TabLink>
79+
<TabLink value="daemons" to="?tab=daemons">
80+
Daemons
81+
</TabLink>
82+
</TabsList>
83+
</Tabs>
84+
85+
<div className="mt-6">
86+
<JobsTabContent org={organization} />
87+
</div>
88+
</main>
89+
</div>
4490
</>
4591
);
4692
};
4793

94+
type JobsTabContentProps = {
95+
org: Organization;
96+
};
97+
98+
const JobsTabContent: FC<JobsTabContentProps> = ({ org }) => {
99+
const { organization } = useOrganizationSettings();
100+
const { data: jobs, isLoadingError } = useQuery(provisionerJobs(org.id));
101+
102+
return (
103+
<section className="flex flex-col gap-8">
104+
<p className="text-sm text-content-secondary m-0 mt-2">
105+
Provisioner Jobs are the individual tasks assigned to Provisioners when
106+
the workspaces are being built.{" "}
107+
<Link href={docs("/admin/provisioners")}>View docs</Link>
108+
</p>
109+
110+
<Table>
111+
<TableHeader>
112+
<TableRow>
113+
<TableHead>Last seen</TableHead>
114+
<TableHead>Type</TableHead>
115+
<TableHead>Template</TableHead>
116+
<TableHead>Tags</TableHead>
117+
<TableHead>Status</TableHead>
118+
</TableRow>
119+
</TableHeader>
120+
<TableBody>
121+
{jobs ? (
122+
jobs.length > 0 ? (
123+
jobs.map(({ metadata, ...job }) => {
124+
if (!metadata) {
125+
throw new Error(
126+
`Metadata is required but it is missing in the job ${job.id}`,
127+
);
128+
}
129+
return (
130+
<TableRow key={job.id}>
131+
<TableCell className="[&:first-letter]:uppercase">
132+
{relativeTime(new Date(job.created_at))}
133+
</TableCell>
134+
<TableCell>
135+
<Badge size="sm">{job.type}</Badge>
136+
</TableCell>
137+
<TableCell>
138+
<div className="flex items-center gap-1">
139+
<Avatar
140+
variant="icon"
141+
src={metadata.template_icon.icon}
142+
fallback={template.display_name || template.name}
143+
/>
144+
{metadata.template_display_name ??
145+
metadata.template_name}
146+
</div>
147+
</TableCell>
148+
<TableCell>
149+
<Badge size="sm">[foo=bar]</Badge>
150+
</TableCell>
151+
<TableCell>Completed</TableCell>
152+
<TableCell className="text-right">
153+
<TooltipProvider>
154+
<Tooltip>
155+
<TooltipTrigger asChild>
156+
<Button
157+
aria-label="Cancel job"
158+
size="icon"
159+
variant="outline"
160+
>
161+
<BanIcon />
162+
</Button>
163+
</TooltipTrigger>
164+
<TooltipContent>Cancel job</TooltipContent>
165+
</Tooltip>
166+
</TooltipProvider>
167+
</TableCell>
168+
</TableRow>
169+
);
170+
})
171+
) : (
172+
<TableEmpty message="No provisioner jobs found" />
173+
)
174+
) : isLoadingError ? (
175+
<TableEmpty message="Error loading the provisioner jobs" />
176+
) : (
177+
<TableLoader />
178+
)}
179+
</TableBody>
180+
</Table>
181+
</section>
182+
);
183+
};
184+
48185
export default OrganizationProvisionersPage;

0 commit comments

Comments
 (0)