From 3cc5274b7ecc7aeda2e684c7bb7d674239d1d7e9 Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Fri, 30 Aug 2024 20:37:54 +0000 Subject: [PATCH 1/9] feat: view provisioners from organization settings --- site/src/api/queries/organizations.ts | 7 + site/src/modules/provisioners/Provisioner.tsx | 125 ++++++++++++ .../modules/provisioners/ProvisionerTag.tsx | 61 ++++++ site/src/pages/HealthPage/Content.tsx | 2 +- .../HealthPage/ProvisionerDaemonsPage.tsx | 181 +----------------- .../OrganizationProvisionersPage.tsx | 68 +++++++ .../OrganizationProvisionersPageView.tsx | 30 +++ .../OrganizationSettingsPage.tsx | 9 +- .../ManagementSettingsPage/SidebarView.tsx | 7 + .../ProvisionerTagsPopover.tsx | 2 +- site/src/router.tsx | 7 + 11 files changed, 323 insertions(+), 176 deletions(-) create mode 100644 site/src/modules/provisioners/Provisioner.tsx create mode 100644 site/src/modules/provisioners/ProvisionerTag.tsx create mode 100644 site/src/pages/ManagementSettingsPage/OrganizationProvisionersPage.tsx create mode 100644 site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.tsx diff --git a/site/src/api/queries/organizations.ts b/site/src/api/queries/organizations.ts index d8386679dd7b7..bca34d896dea4 100644 --- a/site/src/api/queries/organizations.ts +++ b/site/src/api/queries/organizations.ts @@ -223,6 +223,13 @@ export const organizationsPermissions = ( }, action: "create", }, + viewProvisioners: { + object: { + resource_type: "provisioner_daemon", + organization_id: organizationId, + }, + action: "read", + }, }); // The endpoint takes a flat array, so to avoid collisions prepend each diff --git a/site/src/modules/provisioners/Provisioner.tsx b/site/src/modules/provisioners/Provisioner.tsx new file mode 100644 index 0000000000000..a41c98eec78d9 --- /dev/null +++ b/site/src/modules/provisioners/Provisioner.tsx @@ -0,0 +1,125 @@ +import { useTheme } from "@emotion/react"; +import Business from "@mui/icons-material/Business"; +import Person from "@mui/icons-material/Person"; +import SwapHoriz from "@mui/icons-material/SwapHoriz"; +import Tooltip from "@mui/material/Tooltip"; +import type { HealthMessage, ProvisionerDaemon } from "api/typesGenerated"; +import type { FC } from "react"; +import { createDayString } from "utils/createDayString"; +import { ProvisionerTag } from "./ProvisionerTag"; + +// TODO: Importing from a page in here sucks, but idk how to refactor this... +// it's kind of a mess of a file... +import { Pill } from "pages/HealthPage/Content"; + +interface ProvisionerProps { + readonly provisioner: ProvisionerDaemon; + readonly warnings?: readonly HealthMessage[]; +} + +export const Provisioner: FC = ({ + provisioner, + warnings, +}) => { + const theme = useTheme(); + const daemonScope = provisioner.tags.scope || "organization"; + const iconScope = daemonScope === "organization" ? : ; + + const extraTags = Object.entries(provisioner.tags).filter( + ([key]) => key !== "scope" && key !== "owner", + ); + const isWarning = warnings && warnings.length > 0; + return ( +
+
+
+
+

{provisioner.name}

+ + {provisioner.version} + +
+
+
+ + }> + {provisioner.api_version} + + + + + + {daemonScope} + + + + {extraTags.map(([key, value]) => ( + + ))} +
+
+ +
+ {warnings && warnings.length > 0 ? ( +
+ {warnings.map((warning) => ( + {warning.message} + ))} +
+ ) : ( + No warnings + )} + {provisioner.last_seen_at && ( + + Last seen {createDayString(provisioner.last_seen_at)} + + )} +
+
+ ); +}; diff --git a/site/src/modules/provisioners/ProvisionerTag.tsx b/site/src/modules/provisioners/ProvisionerTag.tsx new file mode 100644 index 0000000000000..2d4e9cd2e4534 --- /dev/null +++ b/site/src/modules/provisioners/ProvisionerTag.tsx @@ -0,0 +1,61 @@ +import CloseIcon from "@mui/icons-material/Close"; +import Sell from "@mui/icons-material/Sell"; +import IconButton from "@mui/material/IconButton"; +import type { FC } from "react"; + +// TODO: Importing from a page in here sucks, but idk how to refactor this... +// it's kind of a mess of a file... +import { BooleanPill, Pill } from "pages/HealthPage/Content"; + +const parseBool = (s: string): { valid: boolean; value: boolean } => { + switch (s.toLowerCase()) { + case "true": + case "yes": + case "1": + return { valid: true, value: true }; + case "false": + case "no": + case "0": + case "": + return { valid: true, value: false }; + default: + return { valid: false, value: false }; + } +}; + +interface ProvisionerTagProps { + tagName: string; + tagValue: string; + /** Only used in the TemplateVersionEditor */ + onDelete?: (tagName: string) => void; +} + +export const ProvisionerTag: FC = ({ + tagName, + tagValue, + onDelete, +}) => { + const { valid, value: boolValue } = parseBool(tagValue); + const kv = `${tagName}: ${tagValue}`; + const content = onDelete ? ( + <> + {kv} + { + onDelete(tagName); + }} + > + + + + ) : ( + kv + ); + if (valid) { + return {content}; + } + return }>{content}; +}; diff --git a/site/src/pages/HealthPage/Content.tsx b/site/src/pages/HealthPage/Content.tsx index 32cec0b9f5610..8eae1218f81b3 100644 --- a/site/src/pages/HealthPage/Content.tsx +++ b/site/src/pages/HealthPage/Content.tsx @@ -195,7 +195,7 @@ export const BooleanPill: FC = ({ ...divProps }) => { const theme = useTheme(); - const color = value ? theme.palette.success.light : theme.palette.error.light; + const color = value ? theme.roles.active.outline : theme.roles.danger.outline; return ( { const healthStatus = useOutletContext(); const { provisioner_daemons: daemons } = healthStatus; - const theme = useTheme(); + return ( <> @@ -56,169 +46,16 @@ export const ProvisionerDaemonsPage: FC = () => { ); })} - {daemons.items.map(({ provisioner_daemon: daemon, warnings }) => { - const daemonScope = daemon.tags.scope || "organization"; - const iconScope = - daemonScope === "organization" ? : ; - - const extraTags = Object.entries(daemon.tags).filter( - ([key]) => key !== "scope" && key !== "owner", - ); - const isWarning = warnings.length > 0; - return ( -
-
-
-
-

{daemon.name}

- - {daemon.version} - -
-
-
- - }> - {daemon.api_version} - - - - - - {daemonScope} - - - - {extraTags.map(([key, value]) => ( - - ))} -
-
- -
- {warnings.length > 0 ? ( -
- {warnings.map((warning) => ( - {warning.message} - ))} -
- ) : ( - No warnings - )} - {daemon.last_seen_at && ( - - Last seen {createDayString(daemon.last_seen_at)} - - )} -
-
- ); - })} + {daemons.items.map(({ provisioner_daemon, warnings }) => ( + + ))} ); }; -const parseBool = (s: string): { valid: boolean; value: boolean } => { - switch (s.toLowerCase()) { - case "true": - case "yes": - case "1": - return { valid: true, value: true }; - case "false": - case "no": - case "0": - case "": - return { valid: true, value: false }; - default: - return { valid: false, value: false }; - } -}; - -interface ProvisionerTagProps { - tagName: string; - tagValue: string; - onDelete?: (tagName: string) => void; -} - -export const ProvisionerTag: FC = ({ - tagName, - tagValue, - onDelete, -}) => { - const { valid, value: boolValue } = parseBool(tagValue); - const kv = `${tagName}: ${tagValue}`; - const content = onDelete ? ( - <> - {kv} - { - onDelete(tagName); - }} - > - - - - ) : ( - kv - ); - if (valid) { - return {content}; - } - return }>{content}; -}; - export default ProvisionerDaemonsPage; diff --git a/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPage.tsx b/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPage.tsx new file mode 100644 index 0000000000000..92a2a22817502 --- /dev/null +++ b/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPage.tsx @@ -0,0 +1,68 @@ +import { + organizationsPermissions, + provisionerDaemons, +} from "api/queries/organizations"; +import type { Organization } from "api/typesGenerated"; +import { EmptyState } from "components/EmptyState/EmptyState"; +import { Loader } from "components/Loader/Loader"; +import type { FC } from "react"; +import { useQuery } from "react-query"; +import { useParams } from "react-router-dom"; +import { useOrganizationSettings } from "./ManagementSettingsLayout"; +import { OrganizationProvisionersPageView } from "./OrganizationProvisionersPageView"; +import { ErrorAlert } from "components/Alert/ErrorAlert"; +import NotFoundPage from "pages/404Page/404Page"; + +const OrganizationProvisionersPage: FC = () => { + const { organization: organizationName } = useParams() as { + organization: string; + }; + const { organizations } = useOrganizationSettings(); + + const organization = organizations + ? getOrganizationByName(organizations, organizationName) + : undefined; + const permissionsQuery = useQuery( + organizationsPermissions(organizations?.map((o) => o.id)), + ); + const provisionersQuery = useQuery(provisionerDaemons(organizationName)); + + if (!organization) { + return ; + } + + if (permissionsQuery.isLoading || provisionersQuery.isLoading) { + return ; + } + + const permissions = permissionsQuery.data; + const provisioners = provisionersQuery.data; + const error = permissionsQuery.error || provisionersQuery.error; + if (error || !permissions || !provisioners) { + return ; + } + + // The user may not be able to edit this org but they can still see it because + // they can edit members, etc. In this case they will be shown a read-only + // summary page instead of the settings form. + // Similarly, if the feature is not entitled then the user will not be able to + // edit the organization. + if (!permissions[organization.id]?.viewProvisioners) { + // This probably doesn't work with the layout................fix this pls + // Kayla, hey, yes you, you gotta fix this. + // Don't scroll past this. It's important. Fix it!!! + return ; + } + + return ( + + ); +}; + +export default OrganizationProvisionersPage; + +const getOrganizationByName = (organizations: Organization[], name: string) => + organizations.find((org) => org.name === name); diff --git a/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.tsx b/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.tsx new file mode 100644 index 0000000000000..0e5d3641eb09b --- /dev/null +++ b/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.tsx @@ -0,0 +1,30 @@ +import type { Organization, ProvisionerDaemon } from "api/typesGenerated"; +import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader"; +import { Stack } from "components/Stack/Stack"; +import { Provisioner } from "modules/provisioners/Provisioner"; +import type { FC } from "react"; + +interface OrganizationProvisionersPageViewProps { + organization: Organization; + provisioners: ProvisionerDaemon[]; +} + +export const OrganizationProvisionersPageView: FC< + OrganizationProvisionersPageViewProps +> = ({ organization, provisioners }) => { + return ( +
+ + Provisioners + + + {provisioners.map((provisioner) => ( + + ))} + +
+ ); +}; diff --git a/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.tsx b/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.tsx index bce41745a2d7d..7893b42d5a469 100644 --- a/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.tsx +++ b/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.tsx @@ -17,6 +17,7 @@ import { } from "./ManagementSettingsLayout"; import { OrganizationSettingsPageView } from "./OrganizationSettingsPageView"; import { OrganizationSummaryPageView } from "./OrganizationSummaryPageView"; +import { ErrorAlert } from "components/Alert/ErrorAlert"; const OrganizationSettingsPage: FC = () => { const { organization: organizationName } = useParams() as { @@ -42,11 +43,15 @@ const OrganizationSettingsPage: FC = () => { organizationsPermissions(organizations?.map((o) => o.id)), ); - const permissions = permissionsQuery.data; - if (!organizations || !permissions) { + if (permissionsQuery.isLoading) { return ; } + const permissions = permissionsQuery.data; + if (permissionsQuery.error || !permissions) { + return ; + } + // Redirect /organizations => /organizations/default-org, or if they cannot edit // the default org, then the first org they can edit, if any. if (!organizationName) { diff --git a/site/src/pages/ManagementSettingsPage/SidebarView.tsx b/site/src/pages/ManagementSettingsPage/SidebarView.tsx index d8353a3c43f37..d635279a4d94a 100644 --- a/site/src/pages/ManagementSettingsPage/SidebarView.tsx +++ b/site/src/pages/ManagementSettingsPage/SidebarView.tsx @@ -275,6 +275,13 @@ const OrganizationSettingsNavigation: FC< Roles )} + {organization.permissions.viewProvisioners && ( + + Provisioners + + )} )} diff --git a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx index 7d802fd303c20..f696d42d09660 100644 --- a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx +++ b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx @@ -13,7 +13,7 @@ import { } from "components/Popover/Popover"; import { Stack } from "components/Stack/Stack"; import { useFormik } from "formik"; -import { ProvisionerTag } from "pages/HealthPage/ProvisionerDaemonsPage"; +import { ProvisionerTag } from "modules/provisioners/ProvisionerTag"; import { type FC, Fragment } from "react"; import { docs } from "utils/docs"; import { getFormHelpers, onChangeTrimmed } from "utils/formUtils"; diff --git a/site/src/router.tsx b/site/src/router.tsx index 3513738ab99bc..a85cdb9a31bfb 100644 --- a/site/src/router.tsx +++ b/site/src/router.tsx @@ -251,6 +251,9 @@ const CreateEditRolePage = lazy( () => import("./pages/ManagementSettingsPage/CustomRolesPage/CreateEditRolePage"), ); +const OrganizationProvisionersPage = lazy( + () => import("./pages/ManagementSettingsPage/OrganizationProvisionersPage"), +); const TemplateEmbedPage = lazy( () => import("./pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPage"), ); @@ -399,6 +402,10 @@ export const router = createBrowserRouter( } /> } /> + } + /> From d258246d5ea9a2f4101ec54b87c58a83f2ac4ca9 Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Tue, 3 Sep 2024 21:33:55 +0000 Subject: [PATCH 2/9] =?UTF-8?q?=F0=9F=A7=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HealthPage/ProvisionerDaemonsPage.tsx | 2 +- .../OrganizationProvisionersPage.tsx | 11 ++---- ...ganizationProvisionersPageView.stories.tsx | 29 ++++++++++++++++ .../OrganizationProvisionersPageView.tsx | 16 +++++++-- .../OrganizationSettingsPage.tsx | 2 +- site/src/testHelpers/entities.ts | 34 ++++++++----------- 6 files changed, 62 insertions(+), 32 deletions(-) create mode 100644 site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.stories.tsx diff --git a/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx b/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx index e292266d4a708..e13e0adc810dc 100644 --- a/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx +++ b/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx @@ -1,5 +1,6 @@ import type { HealthcheckReport } from "api/typesGenerated"; import { Alert } from "components/Alert/Alert"; +import { Provisioner } from "modules/provisioners/Provisioner"; import type { FC } from "react"; import { Helmet } from "react-helmet-async"; import { useOutletContext } from "react-router-dom"; @@ -12,7 +13,6 @@ import { Main, } from "./Content"; import { DismissWarningButton } from "./DismissWarningButton"; -import { Provisioner } from "modules/provisioners/Provisioner"; export const ProvisionerDaemonsPage: FC = () => { const healthStatus = useOutletContext(); diff --git a/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPage.tsx b/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPage.tsx index 92a2a22817502..c233826ef07fc 100644 --- a/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPage.tsx +++ b/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPage.tsx @@ -3,15 +3,15 @@ import { provisionerDaemons, } from "api/queries/organizations"; import type { Organization } from "api/typesGenerated"; +import { ErrorAlert } from "components/Alert/ErrorAlert"; import { EmptyState } from "components/EmptyState/EmptyState"; import { Loader } from "components/Loader/Loader"; +import NotFoundPage from "pages/404Page/404Page"; import type { FC } from "react"; import { useQuery } from "react-query"; import { useParams } from "react-router-dom"; import { useOrganizationSettings } from "./ManagementSettingsLayout"; import { OrganizationProvisionersPageView } from "./OrganizationProvisionersPageView"; -import { ErrorAlert } from "components/Alert/ErrorAlert"; -import NotFoundPage from "pages/404Page/404Page"; const OrganizationProvisionersPage: FC = () => { const { organization: organizationName } = useParams() as { @@ -54,12 +54,7 @@ const OrganizationProvisionersPage: FC = () => { return ; } - return ( - - ); + return ; }; export default OrganizationProvisionersPage; diff --git a/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.stories.tsx b/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.stories.tsx new file mode 100644 index 0000000000000..99a0e4494d703 --- /dev/null +++ b/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.stories.tsx @@ -0,0 +1,29 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { MockProvisioner, MockUserProvisioner } from "testHelpers/entities"; +import { OrganizationProvisionersPageView } from "./OrganizationProvisionersPageView"; + +const meta: Meta = { + title: "pages/OrganizationProvisionersPage", + component: OrganizationProvisionersPageView, +}; + +export default meta; +type Story = StoryObj; + +export const Provisioners: Story = { + args: { + provisioners: [ + MockProvisioner, + MockUserProvisioner, + { + ...MockProvisioner, + tags: { + ...MockProvisioner.tags, + 都市: "ユタ", + きっぷ: "yes", + ちいさい: "no", + }, + }, + ], + }, +}; diff --git a/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.tsx b/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.tsx index 0e5d3641eb09b..267439a6b87bf 100644 --- a/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.tsx +++ b/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.tsx @@ -1,22 +1,32 @@ -import type { Organization, ProvisionerDaemon } from "api/typesGenerated"; +import OpenInNewIcon from "@mui/icons-material/OpenInNew"; +import Button from "@mui/material/Button"; +import type { ProvisionerDaemon } from "api/typesGenerated"; import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader"; import { Stack } from "components/Stack/Stack"; import { Provisioner } from "modules/provisioners/Provisioner"; import type { FC } from "react"; +import { docs } from "utils/docs"; interface OrganizationProvisionersPageViewProps { - organization: Organization; provisioners: ProvisionerDaemon[]; } export const OrganizationProvisionersPageView: FC< OrganizationProvisionersPageViewProps -> = ({ organization, provisioners }) => { +> = ({ provisioners }) => { return (
} + href={docs("/admin/provisioners")} + > + Create a provisioner + + } > Provisioners diff --git a/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.tsx b/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.tsx index 7893b42d5a469..5eb1a6e0f4e49 100644 --- a/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.tsx +++ b/site/src/pages/ManagementSettingsPage/OrganizationSettingsPage.tsx @@ -4,6 +4,7 @@ import { updateOrganization, } from "api/queries/organizations"; import type { Organization } from "api/typesGenerated"; +import { ErrorAlert } from "components/Alert/ErrorAlert"; import { EmptyState } from "components/EmptyState/EmptyState"; import { displaySuccess } from "components/GlobalSnackbar/utils"; import { Loader } from "components/Loader/Loader"; @@ -17,7 +18,6 @@ import { } from "./ManagementSettingsLayout"; import { OrganizationSettingsPageView } from "./OrganizationSettingsPageView"; import { OrganizationSummaryPageView } from "./OrganizationSummaryPageView"; -import { ErrorAlert } from "components/Alert/ErrorAlert"; const OrganizationSettingsPage: FC = () => { const { organization: organizationName } = useParams() as { diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 8d09460422251..3948dc148927a 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -542,19 +542,15 @@ export const MockProvisioner: TypesGen.ProvisionerDaemon = { name: "Test Provisioner", provisioners: ["echo"], tags: { scope: "organization" }, - version: "v2.34.5", - api_version: "1.0", + version: MockBuildInfo.version, + api_version: MockBuildInfo.provisioner_api_version, }; export const MockUserProvisioner: TypesGen.ProvisionerDaemon = { - created_at: "2022-05-17T17:39:01.382927298Z", + ...MockProvisioner, id: "test-user-provisioner", - organization_id: MockOrganization.id, name: "Test User Provisioner", - provisioners: ["echo"], tags: { scope: "user", owner: "12345678-abcd-1234-abcd-1234567890abcd" }, - version: "v2.34.5", - api_version: "1.0", }; export const MockProvisionerJob: TypesGen.ProvisionerJob = { @@ -826,7 +822,7 @@ export const MockWorkspaceAgent: TypesGen.WorkspaceAgent = { status: "connected", updated_at: "", version: MockBuildInfo.version, - api_version: "1.0", + api_version: MockBuildInfo.agent_api_version, latency: { "Coder Embedded DERP": { latency_ms: 32.55, @@ -3313,7 +3309,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { created_at: "2023-05-01T19:15:56.606593Z", updated_at: "2023-12-05T14:13:36.647535Z", deleted: false, - version: "v2.5.0-devel+5fad61102", + version: MockBuildInfo.version, }, { id: "9d786ce0-55b1-4ace-8acc-a4672ff8d41f", @@ -3336,7 +3332,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { created_at: "2023-05-01T20:34:11.114005Z", updated_at: "2023-12-05T14:13:45.941716Z", deleted: false, - version: "v2.5.0-devel+5fad61102", + version: MockBuildInfo.version, }, { id: "2e209786-73b1-4838-ba78-e01c9334450a", @@ -3359,7 +3355,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { created_at: "2023-05-01T20:41:02.76448Z", updated_at: "2023-12-05T14:13:41.968568Z", deleted: false, - version: "v2.5.0-devel+5fad61102", + version: MockBuildInfo.version, }, { id: "c272e80c-0cce-49d6-9782-1b5cf90398e8", @@ -3430,7 +3426,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { created_at: "2023-12-01T09:21:15.996267Z", updated_at: "2023-12-05T14:13:59.663174Z", deleted: false, - version: "v2.5.0-devel+5fad61102", + version: MockBuildInfo.version, }, { id: "72649dc9-03c7-46a8-bc95-96775e93ddc1", @@ -3453,7 +3449,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { created_at: "2023-12-01T09:23:44.505529Z", updated_at: "2023-12-05T14:13:55.769058Z", deleted: false, - version: "v2.5.0-devel+5fad61102", + version: MockBuildInfo.version, }, { id: "1f78398f-e5ae-4c38-aa89-30222181d443", @@ -3476,7 +3472,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { created_at: "2023-12-01T09:36:00.231252Z", updated_at: "2023-12-05T14:13:47.015031Z", deleted: false, - version: "v2.5.0-devel+5fad61102", + version: MockBuildInfo.version, }, ], }, @@ -3502,8 +3498,8 @@ export const MockHealth: TypesGen.HealthcheckReport = { created_at: "2024-01-04T15:53:03.21563Z", last_seen_at: "2024-01-04T16:05:03.967551Z", name: "ok", - version: "v2.3.4-devel+abcd1234", - api_version: "1.0", + version: MockBuildInfo.version, + api_version: MockBuildInfo.provisioner_api_version, provisioners: ["echo", "terraform"], tags: { owner: "", @@ -3523,8 +3519,8 @@ export const MockHealth: TypesGen.HealthcheckReport = { created_at: "2024-01-04T15:53:03.21563Z", last_seen_at: "2024-01-04T16:05:03.967551Z", name: "user-scoped", - version: "v2.34-devel+abcd1234", - api_version: "1.0", + version: MockBuildInfo.version, + api_version: MockBuildInfo.provisioner_api_version, provisioners: ["echo", "terraform"], tags: { owner: "12345678-1234-1234-1234-12345678abcd", @@ -3569,7 +3565,7 @@ export const MockHealth: TypesGen.HealthcheckReport = { }, ], }, - coder_version: "v2.5.0-devel+5fad61102", + coder_version: MockBuildInfo.version, }; export const MockListeningPortsResponse: TypesGen.WorkspaceAgentListeningPortsResponse = From 6eca545fc94821ee1d09bae5cf0f563ea9f5a78d Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Tue, 3 Sep 2024 22:07:06 +0000 Subject: [PATCH 3/9] new pills! --- site/src/components/Pill/Pill.tsx | 23 +++++++-- site/src/modules/provisioners/Provisioner.tsx | 28 ++++------- .../modules/provisioners/ProvisionerTag.tsx | 48 ++++++++++++++++--- 3 files changed, 72 insertions(+), 27 deletions(-) diff --git a/site/src/components/Pill/Pill.tsx b/site/src/components/Pill/Pill.tsx index 8462b741e4869..cd403c9db826b 100644 --- a/site/src/components/Pill/Pill.tsx +++ b/site/src/components/Pill/Pill.tsx @@ -14,6 +14,7 @@ import type { ThemeRole } from "theme/roles"; export type PillProps = HTMLAttributes & { icon?: ReactNode; type?: ThemeRole; + size?: "md" | "lg"; }; const themeStyles = (type: ThemeRole) => (theme: Theme) => { @@ -30,13 +31,24 @@ const PILL_ICON_SPACING = (PILL_HEIGHT - PILL_ICON_SIZE) / 2; export const Pill: FC = forwardRef( (props, ref) => { - const { icon, type = "inactive", children, ...divProps } = props; + const { + icon, + type = "inactive", + children, + size = "md", + ...divProps + } = props; const typeStyles = useMemo(() => themeStyles(type), [type]); return (
{icon} @@ -77,7 +89,12 @@ const styles = { }), pillWithIcon: { - paddingLeft: PILL_ICON_SPACING, + paddingLeft: PILL_ICON_SPACING * 2, + }, + + pillLg: { + gap: PILL_ICON_SPACING * 2, + padding: "14px 16px", }, spinner: (theme) => ({ diff --git a/site/src/modules/provisioners/Provisioner.tsx b/site/src/modules/provisioners/Provisioner.tsx index a41c98eec78d9..454e85025c164 100644 --- a/site/src/modules/provisioners/Provisioner.tsx +++ b/site/src/modules/provisioners/Provisioner.tsx @@ -1,17 +1,13 @@ import { useTheme } from "@emotion/react"; import Business from "@mui/icons-material/Business"; import Person from "@mui/icons-material/Person"; -import SwapHoriz from "@mui/icons-material/SwapHoriz"; import Tooltip from "@mui/material/Tooltip"; import type { HealthMessage, ProvisionerDaemon } from "api/typesGenerated"; +import { Pill } from "components/Pill/Pill"; import type { FC } from "react"; import { createDayString } from "utils/createDayString"; import { ProvisionerTag } from "./ProvisionerTag"; -// TODO: Importing from a page in here sucks, but idk how to refactor this... -// it's kind of a mess of a file... -import { Pill } from "pages/HealthPage/Content"; - interface ProvisionerProps { readonly provisioner: ProvisionerDaemon; readonly warnings?: readonly HealthMessage[]; @@ -32,13 +28,14 @@ export const Provisioner: FC = ({ return (
= ({ gap: 12, }} > - - }> - {provisioner.api_version} - - - + { switch (s.toLowerCase()) { @@ -57,5 +57,41 @@ export const ProvisionerTag: FC = ({ if (valid) { return {content}; } - return }>{content}; + return }>{content}; +}; + +type BooleanPillProps = Omit, "icon" | "value"> & { + value: boolean; +}; + +export const BooleanPill: FC = ({ + value, + children, + ...divProps +}) => { + return ( + + ) : ( + + ) + } + {...divProps} + > + {children} + + ); }; + +const styles = { + truePill: (theme) => ({ + color: theme.roles.active.outline, + }), + falsePill: (theme) => ({ + color: theme.roles.danger.outline, + }), +} satisfies Record>; From 89b462951eebf0d2993a06138e02761efb7bb3ba Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Tue, 3 Sep 2024 22:09:42 +0000 Subject: [PATCH 4/9] >:( --- site/src/modules/provisioners/ProvisionerTag.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/site/src/modules/provisioners/ProvisionerTag.tsx b/site/src/modules/provisioners/ProvisionerTag.tsx index 942a5b8437c9c..7f108ff9e94a8 100644 --- a/site/src/modules/provisioners/ProvisionerTag.tsx +++ b/site/src/modules/provisioners/ProvisionerTag.tsx @@ -57,7 +57,11 @@ export const ProvisionerTag: FC = ({ if (valid) { return {content}; } - return }>{content}; + return ( + }> + {content} + + ); }; type BooleanPillProps = Omit, "icon" | "value"> & { From e8f33fd826a978015be65a7d2021bbd48d82b260 Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Tue, 3 Sep 2024 22:09:59 +0000 Subject: [PATCH 5/9] >>::(( --- site/src/modules/provisioners/ProvisionerTag.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/site/src/modules/provisioners/ProvisionerTag.tsx b/site/src/modules/provisioners/ProvisionerTag.tsx index 7f108ff9e94a8..50cb9e25fa6f8 100644 --- a/site/src/modules/provisioners/ProvisionerTag.tsx +++ b/site/src/modules/provisioners/ProvisionerTag.tsx @@ -1,11 +1,11 @@ +import type { Interpolation, Theme } from "@emotion/react"; +import CheckCircleOutlined from "@mui/icons-material/CheckCircleOutlined"; import CloseIcon from "@mui/icons-material/Close"; +import DoNotDisturbOnOutlined from "@mui/icons-material/DoNotDisturbOnOutlined"; import Sell from "@mui/icons-material/Sell"; import IconButton from "@mui/material/IconButton"; -import type { ComponentProps, FC } from "react"; import { Pill } from "components/Pill/Pill"; -import type { Interpolation, Theme } from "@emotion/react"; -import CheckCircleOutlined from "@mui/icons-material/CheckCircleOutlined"; -import DoNotDisturbOnOutlined from "@mui/icons-material/DoNotDisturbOnOutlined"; +import type { ComponentProps, FC } from "react"; const parseBool = (s: string): { valid: boolean; value: boolean } => { switch (s.toLowerCase()) { From 1cc89f210ad3db6532baa6107ffb7ac5872ceb23 Mon Sep 17 00:00:00 2001 From: Kayla Washburn-Love Date: Wed, 4 Sep 2024 14:04:17 -0600 Subject: [PATCH 6/9] Update site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.tsx Co-authored-by: Jaayden Halko --- .../ManagementSettingsPage/OrganizationProvisionersPageView.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.tsx b/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.tsx index 267439a6b87bf..4374c02833183 100644 --- a/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.tsx +++ b/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.tsx @@ -22,6 +22,7 @@ export const OrganizationProvisionersPageView: FC< actions={