diff --git a/site/src/api/api.ts b/site/src/api/api.ts index fa62afadee608..6667ad98d8d13 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -396,7 +396,17 @@ export class MissingBuildParameters extends Error { } export type GetProvisionerJobsParams = { - status?: TypesGen.ProvisionerJobStatus; + status?: string; + limit?: number; + // IDs separated by comma + ids?: string; +}; + +export type GetProvisionerDaemonsParams = { + // IDs separated by comma + ids?: string; + // Stringified JSON Object + tags?: string; limit?: number; }; @@ -711,22 +721,13 @@ class ApiMethods { return response.data; }; - /** - * @param organization Can be the organization's ID or name - * @param tags to filter provisioner daemons by. - */ getProvisionerDaemonsByOrganization = async ( organization: string, - tags?: Record, + params?: GetProvisionerDaemonsParams, ): Promise => { - const params = new URLSearchParams(); - - if (tags) { - params.append("tags", JSON.stringify(tags)); - } - const response = await this.axios.get( - `/api/v2/organizations/${organization}/provisionerdaemons?${params}`, + `/api/v2/organizations/${organization}/provisionerdaemons`, + { params }, ); return response.data; }; diff --git a/site/src/api/queries/organizations.ts b/site/src/api/queries/organizations.ts index 632b5f0c730ad..238fb4493fb52 100644 --- a/site/src/api/queries/organizations.ts +++ b/site/src/api/queries/organizations.ts @@ -1,4 +1,8 @@ -import { API, type GetProvisionerJobsParams } from "api/api"; +import { + API, + type GetProvisionerDaemonsParams, + type GetProvisionerJobsParams, +} from "api/api"; import type { CreateOrganizationRequest, GroupSyncSettings, @@ -164,16 +168,17 @@ export const organizations = () => { export const getProvisionerDaemonsKey = ( organization: string, - tags?: Record, -) => ["organization", organization, tags, "provisionerDaemons"]; + params?: GetProvisionerDaemonsParams, +) => ["organization", organization, "provisionerDaemons", params]; export const provisionerDaemons = ( organization: string, - tags?: Record, + params?: GetProvisionerDaemonsParams, ) => { return { - queryKey: getProvisionerDaemonsKey(organization, tags), - queryFn: () => API.getProvisionerDaemonsByOrganization(organization, tags), + queryKey: getProvisionerDaemonsKey(organization, params), + queryFn: () => + API.getProvisionerDaemonsByOrganization(organization, params), }; }; diff --git a/site/src/components/Button/Button.tsx b/site/src/components/Button/Button.tsx index d9daae9c59252..1a01588af341a 100644 --- a/site/src/components/Button/Button.tsx +++ b/site/src/components/Button/Button.tsx @@ -8,7 +8,7 @@ import { forwardRef } from "react"; import { cn } from "utils/cn"; export const buttonVariants = cva( - `inline-flex items-center justify-center gap-1 whitespace-nowrap + `inline-flex items-center justify-center gap-1 whitespace-nowrap font-sans border-solid rounded-md transition-colors text-sm font-semibold font-medium cursor-pointer no-underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-content-link @@ -30,6 +30,7 @@ export const buttonVariants = cva( size: { lg: "min-w-20 h-10 px-3 py-2 [&_svg]:size-icon-lg", sm: "min-w-20 h-8 px-2 py-1.5 text-xs [&_svg]:size-icon-sm", + xs: "min-w-8 py-1 px-2 text-2xs rounded-md", icon: "size-8 px-1.5 [&_svg]:size-icon-sm", "icon-lg": "size-10 px-2 [&_svg]:size-icon-lg", }, diff --git a/site/src/pages/CreateTokenPage/CreateTokenForm.tsx b/site/src/pages/CreateTokenPage/CreateTokenForm.tsx index ee5c3bf8f3a6e..57d1587e92590 100644 --- a/site/src/pages/CreateTokenPage/CreateTokenForm.tsx +++ b/site/src/pages/CreateTokenPage/CreateTokenForm.tsx @@ -119,7 +119,6 @@ export const CreateTokenForm: FC = ({ {lifetimeDays === "custom" && ( = ({ setExpDays(lt); }} inputProps={{ + "data-chromatic": "ignore", min: dayjs().add(1, "day").format("YYYY-MM-DD"), max: maxTokenLifetime ? dayjs() diff --git a/site/src/pages/OrganizationSettingsPage/CustomRolesPage/PermissionPillsList.stories.tsx b/site/src/pages/OrganizationSettingsPage/CustomRolesPage/PermissionPillsList.stories.tsx index 56eb382067d84..7a62a8f955747 100644 --- a/site/src/pages/OrganizationSettingsPage/CustomRolesPage/PermissionPillsList.stories.tsx +++ b/site/src/pages/OrganizationSettingsPage/CustomRolesPage/PermissionPillsList.stories.tsx @@ -15,7 +15,7 @@ const meta: Meta = { ], parameters: { chromatic: { - diffThreshold: 0.5, + diffThreshold: 0.6, }, }, }; diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/JobRow.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/JobRow.tsx index 3e20863b25d51..e97749db3d6f4 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/JobRow.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/JobRow.tsx @@ -15,17 +15,19 @@ import { ProvisionerTruncateTags, } from "modules/provisioners/ProvisionerTags"; import { type FC, useState } from "react"; +import { Link as RouterLink } from "react-router-dom"; import { cn } from "utils/cn"; import { relativeTime } from "utils/time"; import { CancelJobButton } from "./CancelJobButton"; type JobRowProps = { job: ProvisionerJob; + defaultIsOpen: boolean; }; -export const JobRow: FC = ({ job }) => { +export const JobRow: FC = ({ job, defaultIsOpen = false }) => { const metadata = job.metadata; - const [isOpen, setIsOpen] = useState(false); + const [isOpen, setIsOpen] = useState(defaultIsOpen); const queue = { size: job.queue_size, position: job.queue_position, @@ -114,8 +116,21 @@ export const JobRow: FC = ({ job }) => { : "[]"} -
Completed by provisioner:
-
{job.worker_id}
+ {job.worker_id && ( + <> +
Completed by provisioner:
+
+ {job.worker_id} + +
+ + )}
Associated workspace:
{job.metadata.workspace_name ?? "null"}
@@ -123,10 +138,14 @@ export const JobRow: FC = ({ job }) => {
Creation time:
{job.created_at}
-
Queue:
-
- {job.queue_position}/{job.queue_size} -
+ {job.queue_position > 0 && ( + <> +
Queue:
+
+ {job.queue_position}/{job.queue_size} +
+ + )}
Tags:
diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPage.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPage.tsx index 8602fe0c23727..e7c8e30efcf17 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPage.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPage.tsx @@ -11,18 +11,18 @@ const OrganizationProvisionerJobsPage: FC = () => { const { organization } = useOrganizationSettings(); const [searchParams, setSearchParams] = useSearchParams(); const filter = { - status: searchParams.get("status") || "", + status: searchParams.get("status") ?? "", + ids: searchParams.get("ids") ?? "", }; - const queryParams = { - ...filter, - limit: 100, - } as GetProvisionerJobsParams; const { data: jobs, isLoadingError, refetch, } = useQuery({ - ...provisionerJobs(organization?.id || "", queryParams), + ...provisionerJobs(organization?.id ?? "", { + ...filter, + limit: 100, + }), enabled: organization !== undefined, }); diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPageView.stories.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPageView.stories.tsx index a5837cf527fc2..35a96a1b3bd5f 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPageView.stories.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPageView.stories.tsx @@ -21,7 +21,7 @@ const meta: Meta = { args: { organization: MockOrganization, jobs: MockProvisionerJobs, - filter: { status: "" }, + filter: { status: "", ids: "" }, onRetry: fn(), }, }; @@ -81,8 +81,8 @@ export const Empty: Story = { export const OnFilter: Story = { render: function FilterWithState({ ...args }) { const [jobs, setJobs] = useState([]); - const [filter, setFilter] = useState({ status: "pending" }); - const handleFilterChange = (newFilter: { status: string }) => { + const [filter, setFilter] = useState({ status: "pending", ids: "" }); + const handleFilterChange = (newFilter: { status: string; ids: string }) => { setFilter(newFilter); const filteredJobs = MockProvisionerJobs.filter((job) => newFilter.status ? job.status === newFilter.status : true, @@ -109,3 +109,13 @@ export const OnFilter: Story = { await userEvent.click(option); }, }; + +export const FilterByID: Story = { + args: { + jobs: [MockProvisionerJob], + filter: { + ids: MockProvisionerJob.id, + status: "", + }, + }, +}; diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPageView.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPageView.tsx index 6aa372c7c6205..8b6a2a839b8af 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPageView.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPageView.tsx @@ -3,6 +3,7 @@ import type { ProvisionerJob, ProvisionerJobStatus, } from "api/typesGenerated"; +import { Badge } from "components/Badge/Badge"; import { Button } from "components/Button/Button"; import { EmptyState } from "components/EmptyState/EmptyState"; import { Link } from "components/Link/Link"; @@ -33,6 +34,13 @@ import { TableHeader, TableRow, } from "components/Table/Table"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; +import { XIcon } from "lucide-react"; import type { FC } from "react"; import { Helmet } from "react-helmet-async"; import { docs } from "utils/docs"; @@ -64,6 +72,7 @@ const StatusFilters: ProvisionerJobStatus[] = [ type JobProvisionersFilter = { status: string; + ids: string; }; type OrganizationProvisionerJobsPageViewProps = { @@ -110,30 +119,62 @@ const OrganizationProvisionerJobsPageView: FC< - +
+ {filter.ids && ( +
+ + {filter.ids} + +
+ + + + + + Clear ID + + +
+
+ )} + + +
@@ -149,7 +190,13 @@ const OrganizationProvisionerJobsPageView: FC< {jobs ? ( jobs.length > 0 ? ( - jobs.map((j) => ) + jobs.map((j) => ( + + )) ) : ( diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPage.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPage.tsx index 181bbbb4c62a3..242c0acdf842b 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPage.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPage.tsx @@ -8,7 +8,7 @@ import { RequirePermission } from "modules/permissions/RequirePermission"; import type { FC } from "react"; import { Helmet } from "react-helmet-async"; import { useQuery } from "react-query"; -import { useParams } from "react-router-dom"; +import { useParams, useSearchParams } from "react-router-dom"; import { pageTitle } from "utils/page"; import { OrganizationProvisionersPageView } from "./OrganizationProvisionersPageView"; @@ -16,14 +16,20 @@ const OrganizationProvisionersPage: FC = () => { const { organization: organizationName } = useParams() as { organization: string; }; + const [searchParams, setSearchParams] = useSearchParams(); + const queryParams = { + ids: searchParams.get("ids") ?? "", + tags: searchParams.get("tags") ?? "", + }; const { organization, organizationPermissions } = useOrganizationSettings(); const { entitlements } = useDashboard(); const { metadata } = useEmbeddedMetadata(); const buildInfoQuery = useQuery(buildInfo(metadata["build-info"])); const provisionersQuery = useQuery({ - ...provisionerDaemons(organizationName), - select: (provisioners) => - provisioners.filter((p) => p.status !== "offline"), + ...provisionerDaemons(organizationName, { + ...queryParams, + limit: 100, + }), }); if (!organization) { @@ -59,6 +65,8 @@ const OrganizationProvisionersPage: FC = () => { provisioners={provisionersQuery.data} buildVersion={buildInfoQuery.data?.version} onRetry={provisionersQuery.refetch} + filter={queryParams} + onFilterChange={setSearchParams} /> ); diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPageView.stories.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPageView.stories.tsx index 93d47e97d6a9f..a559af512bbe3 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPageView.stories.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPageView.stories.tsx @@ -24,6 +24,9 @@ const meta: Meta = { version: "0.0.0", }, ], + filter: { + ids: "", + }, }, }; @@ -60,3 +63,12 @@ export const Paywall: Story = { showPaywall: true, }, }; + +export const FilterByID: Story = { + args: { + provisioners: [MockProvisioner], + filter: { + ids: MockProvisioner.id, + }, + }, +}; diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPageView.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPageView.tsx index e0ccddd9f5448..387baf31519cb 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPageView.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/OrganizationProvisionersPageView.tsx @@ -1,4 +1,5 @@ import type { ProvisionerDaemon } from "api/typesGenerated"; +import { Badge } from "components/Badge/Badge"; import { Button } from "components/Button/Button"; import { EmptyState } from "components/EmptyState/EmptyState"; import { Link } from "components/Link/Link"; @@ -17,23 +18,43 @@ import { TableHeader, TableRow, } from "components/Table/Table"; -import { SquareArrowOutUpRightIcon } from "lucide-react"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; +import { SquareArrowOutUpRightIcon, XIcon } from "lucide-react"; import type { FC } from "react"; import { docs } from "utils/docs"; import { LastConnectionHead } from "./LastConnectionHead"; import { ProvisionerRow } from "./ProvisionerRow"; +type ProvisionersFilter = { + ids: string; +}; + interface OrganizationProvisionersPageViewProps { showPaywall: boolean | undefined; provisioners: readonly ProvisionerDaemon[] | undefined; buildVersion: string | undefined; error: unknown; + filter: ProvisionersFilter; onRetry: () => void; + onFilterChange: (filter: ProvisionersFilter) => void; } export const OrganizationProvisionersPageView: FC< OrganizationProvisionersPageViewProps -> = ({ showPaywall, error, provisioners, buildVersion, onRetry }) => { +> = ({ + showPaywall, + error, + provisioners, + buildVersion, + filter, + onFilterChange, + onRetry, +}) => { return (
@@ -45,6 +66,35 @@ export const OrganizationProvisionersPageView: FC< + {filter.ids && ( +
+
+ + {filter.ids} + +
+ + + + + + Clear ID + + +
+
+
+ )} + {showPaywall ? ( )) ) : ( diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/ProvisionerRow.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/ProvisionerRow.tsx index 2c47578f67a6a..ca5af240d1b02 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/ProvisionerRow.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage/ProvisionerRow.tsx @@ -18,6 +18,7 @@ import { } from "modules/provisioners/ProvisionerTags"; import { ProvisionerKey } from "pages/OrganizationSettingsPage/OrganizationProvisionersPage/ProvisionerKey"; import { type FC, useState } from "react"; +import { Link as RouterLink } from "react-router-dom"; import { cn } from "utils/cn"; import { relativeTime } from "utils/time"; import { ProvisionerVersion } from "./ProvisionerVersion"; @@ -34,13 +35,15 @@ const variantByStatus: Record< type ProvisionerRowProps = { provisioner: ProvisionerDaemon; buildVersion: string | undefined; + defaultIsOpen: boolean; }; export const ProvisionerRow: FC = ({ provisioner, buildVersion, + defaultIsOpen = false, }) => { - const [isOpen, setIsOpen] = useState(false); + const [isOpen, setIsOpen] = useState(defaultIsOpen); return ( <> @@ -151,7 +154,16 @@ export const ProvisionerRow: FC = ({ {provisioner.previous_job && ( <>
Previous job:
-
{provisioner.previous_job.id}
+
+ {provisioner.previous_job.id} + +
Previous job status:
diff --git a/site/src/pages/TerminalPage/TerminalPage.stories.tsx b/site/src/pages/TerminalPage/TerminalPage.stories.tsx index 7a34d57fbf83d..d58f3e328e3ff 100644 --- a/site/src/pages/TerminalPage/TerminalPage.stories.tsx +++ b/site/src/pages/TerminalPage/TerminalPage.stories.tsx @@ -91,7 +91,7 @@ const meta = { }, ], chromatic: { - diffThreshold: 0.5, + diffThreshold: 0.8, }, }, decorators: [ diff --git a/site/src/pages/WorkspacePage/WorkspaceTopbar.stories.tsx b/site/src/pages/WorkspacePage/WorkspaceTopbar.stories.tsx index 1ae3ff9e2ebc9..ce2ad840a1df0 100644 --- a/site/src/pages/WorkspacePage/WorkspaceTopbar.stories.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceTopbar.stories.tsx @@ -39,7 +39,7 @@ const meta: Meta = { layout: "fullscreen", features: ["advanced_template_scheduling"], chromatic: { - diffThreshold: 0.3, + diffThreshold: 0.6, }, }, }; @@ -321,7 +321,7 @@ export const TemplateInfoPopover: Story = { }, parameters: { chromatic: { - diffThreshold: 0.3, + diffThreshold: 0.6, }, }, }; diff --git a/site/tailwind.config.js b/site/tailwind.config.js index 142a4711b56f3..d2935698e5d9e 100644 --- a/site/tailwind.config.js +++ b/site/tailwind.config.js @@ -8,6 +8,9 @@ module.exports = { important: ["#root", "#storybook-root"], theme: { extend: { + fontFamily: { + sans: `"Inter Variable", system-ui, sans-serif`, + }, size: { "icon-lg": "1.5rem", "icon-sm": "1.125rem", pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy