-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Add default workspace image to org setting #18723
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
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
b7dd2e6
Add default workspace image to org setting
mustard-mh 5cb3118
[db] add migration
mustard-mh 90a851e
fixup
mustard-mh 2c6c5d1
[dashboard] add org default image settings
mustard-mh 03262da
fixup
mustard-mh 88e8909
dashboard fixup
mustard-mh bc074ba
Add server image test TODO
mustard-mh 5080271
[server] assign global workspace default image
mustard-mh 7652208
[dashboard] allow to submit empty string (will fallback to global def…
mustard-mh d22e969
[gp-cli] support gp validate with default image
mustard-mh 7e6e29c
[dashboard] save default image
mustard-mh f347e3b
fixup
mustard-mh 0a7b1ff
fixup
mustard-mh dea9564
[gp-cli] improve output
mustard-mh ae32c3a
[gp-cli] improve gp init
mustard-mh 2771ff4
[gp-cli] gp validate compatibility
mustard-mh e2a7849
Update components/dashboard/src/teams/TeamSettings.tsx
mustard-mh f875e63
Remove org id / get org settings in supervisor
mustard-mh 2756caf
Remove `WorkspaceConfigContext`
mustard-mh c8ccb52
Add unit tests
mustard-mh 8dd6695
Rename to `DefaultWorkspaceImage`
mustard-mh 06cfd08
Update components/dashboard/src/teams/TeamSettings.tsx
mustard-mh 1941376
Add empty image fallback to supervisor
mustard-mh c2df290
Fix default workspace image setup
mustard-mh 4987594
Update org settings fields
mustard-mh 5f0daea
fixup
mustard-mh 97ae60f
Allow empty image to set to default one
mustard-mh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ | |
*/ | ||
|
||
import { OrganizationSettings } from "@gitpod/gitpod-protocol"; | ||
import React, { useCallback, useState } from "react"; | ||
import React, { useCallback, useState, useEffect } from "react"; | ||
import Alert from "../components/Alert"; | ||
import { Button } from "../components/Button"; | ||
import { CheckboxInputField } from "../components/forms/CheckboxInputField"; | ||
|
@@ -14,13 +14,14 @@ import { TextInputField } from "../components/forms/TextInputField"; | |
import { Heading2, Subheading } from "../components/typography/headings"; | ||
import { useUpdateOrgSettingsMutation } from "../data/organizations/update-org-settings-mutation"; | ||
import { useOrgSettingsQuery } from "../data/organizations/org-settings-query"; | ||
import { useCurrentOrg, useOrganizationsInvalidator } from "../data/organizations/orgs-query"; | ||
import { OrganizationInfo, useCurrentOrg, useOrganizationsInvalidator } from "../data/organizations/orgs-query"; | ||
import { useUpdateOrgMutation } from "../data/organizations/update-org-mutation"; | ||
import { useOnBlurError } from "../hooks/use-onblur-error"; | ||
import { teamsService } from "../service/public-api"; | ||
import { gitpodHostUrl } from "../service/service"; | ||
import { useCurrentUser } from "../user-context"; | ||
import { OrgSettingsPage } from "./OrgSettingsPage"; | ||
import { useToast } from "../components/toasts/Toasts"; | ||
|
||
export default function TeamSettingsPage() { | ||
const user = useCurrentUser(); | ||
|
@@ -31,21 +32,6 @@ export default function TeamSettingsPage() { | |
const [teamName, setTeamName] = useState(org?.name || ""); | ||
const [updated, setUpdated] = useState(false); | ||
const updateOrg = useUpdateOrgMutation(); | ||
const { data: settings, isLoading } = useOrgSettingsQuery(); | ||
const updateTeamSettings = useUpdateOrgSettingsMutation(); | ||
|
||
const handleUpdateTeamSettings = useCallback( | ||
(newSettings: Partial<OrganizationSettings>) => { | ||
if (!org?.id) { | ||
throw new Error("no organization selected"); | ||
} | ||
updateTeamSettings.mutate({ | ||
...settings, | ||
...newSettings, | ||
}); | ||
}, | ||
[updateTeamSettings, org?.id, settings], | ||
); | ||
|
||
const close = () => setModal(false); | ||
|
||
|
@@ -60,6 +46,9 @@ export default function TeamSettingsPage() { | |
|
||
const updateTeamInformation = useCallback( | ||
async (e: React.FormEvent) => { | ||
if (!org?.isOwner) { | ||
return; | ||
} | ||
e.preventDefault(); | ||
|
||
if (!orgFormIsValid) { | ||
|
@@ -74,7 +63,7 @@ export default function TeamSettingsPage() { | |
console.error(error); | ||
} | ||
}, | ||
[orgFormIsValid, updateOrg, teamName], | ||
[orgFormIsValid, updateOrg, teamName, org], | ||
); | ||
|
||
const deleteTeam = useCallback(async () => { | ||
|
@@ -99,12 +88,6 @@ export default function TeamSettingsPage() { | |
<span>{updateOrg.error.message || "unknown error"}</span> | ||
</Alert> | ||
)} | ||
{updateTeamSettings.isError && ( | ||
<Alert type="error" closable={true} className="mb-2 max-w-xl rounded-md"> | ||
<span>Failed to update organization settings: </span> | ||
<span>{updateTeamSettings.error.message || "unknown error"}</span> | ||
</Alert> | ||
)} | ||
{updated && ( | ||
<Alert type="message" closable={true} className="mb-2 max-w-xl rounded-md"> | ||
Organization name has been updated. | ||
|
@@ -117,30 +100,27 @@ export default function TeamSettingsPage() { | |
value={teamName} | ||
error={teamNameError.message} | ||
onChange={setTeamName} | ||
disabled={!org?.isOwner} | ||
onBlur={teamNameError.onBlur} | ||
/> | ||
|
||
<Button className="mt-4" htmlType="submit" disabled={org?.name === teamName || !orgFormIsValid}> | ||
Update Organization | ||
</Button> | ||
|
||
<Heading2 className="pt-12">Collaboration & Sharing</Heading2> | ||
<CheckboxInputField | ||
label="Workspace Sharing" | ||
hint="Allow workspaces created within an Organization to share the workspace with any authenticated user." | ||
checked={!settings?.workspaceSharingDisabled} | ||
onChange={(checked) => handleUpdateTeamSettings({ workspaceSharingDisabled: !checked })} | ||
disabled={isLoading} | ||
/> | ||
{org?.isOwner && ( | ||
<Button className="mt-4" htmlType="submit" disabled={org?.name === teamName || !orgFormIsValid}> | ||
Update Organization | ||
</Button> | ||
)} | ||
</form> | ||
|
||
{user?.organizationId !== org?.id && ( | ||
<OrgSettingsForm org={org} /> | ||
|
||
{user?.organizationId !== org?.id && org?.isOwner && ( | ||
<> | ||
<Heading2 className="pt-12">Delete Organization</Heading2> | ||
<Subheading className="pb-4 max-w-2xl"> | ||
Deleting this organization will also remove all associated data, including projects and | ||
workspaces. Deleted organizations cannot be restored! | ||
</Subheading> | ||
|
||
<button className="danger secondary" onClick={() => setModal(true)}> | ||
Delete Organization | ||
</button> | ||
|
@@ -185,3 +165,92 @@ export default function TeamSettingsPage() { | |
</> | ||
); | ||
} | ||
|
||
function OrgSettingsForm(props: { org?: OrganizationInfo }) { | ||
const { org } = props; | ||
const { data: settings, isLoading } = useOrgSettingsQuery(); | ||
const updateTeamSettings = useUpdateOrgSettingsMutation(); | ||
const [defaultWorkspaceImage, setDefaultWorkspaceImage] = useState(settings?.defaultWorkspaceImage ?? ""); | ||
const { toast } = useToast(); | ||
|
||
useEffect(() => { | ||
if (!settings) { | ||
return; | ||
} | ||
setDefaultWorkspaceImage(settings.defaultWorkspaceImage ?? ""); | ||
}, [settings]); | ||
|
||
const handleUpdateTeamSettings = useCallback( | ||
async (newSettings: Partial<OrganizationSettings>) => { | ||
if (!org?.id) { | ||
throw new Error("no organization selected"); | ||
} | ||
if (!org.isOwner) { | ||
throw new Error("no organization settings change permission"); | ||
} | ||
try { | ||
await updateTeamSettings.mutateAsync({ | ||
// We don't want to have original setting passed, since defaultWorkspaceImage could be undefined | ||
// to bring compatibility when we're going to change Gitpod install value's defaultImage setting | ||
...newSettings, | ||
}); | ||
if (newSettings.defaultWorkspaceImage) { | ||
toast("Default workspace image has been updated."); | ||
} | ||
} catch (error) { | ||
console.error(error); | ||
toast( | ||
error.message | ||
? "Failed to update organization settings: " + error.message | ||
: "Oh no, there was a problem with our service.", | ||
); | ||
} | ||
}, | ||
[updateTeamSettings, org?.id, org?.isOwner, toast], | ||
); | ||
|
||
return ( | ||
<form | ||
onSubmit={(e) => { | ||
e.preventDefault(); | ||
handleUpdateTeamSettings({ defaultWorkspaceImage }); | ||
}} | ||
> | ||
<Heading2 className="pt-12">Collaboration & Sharing</Heading2> | ||
<Subheading className="max-w-2xl"> | ||
Choose which workspace images you want to use for your workspaces. | ||
</Subheading> | ||
|
||
{updateTeamSettings.isError && ( | ||
<Alert type="error" closable={true} className="mb-2 max-w-xl rounded-md"> | ||
<span>Failed to update organization settings: </span> | ||
<span>{updateTeamSettings.error.message || "unknown error"}</span> | ||
</Alert> | ||
)} | ||
|
||
<CheckboxInputField | ||
label="Workspace Sharing" | ||
hint="Allow workspaces created within an Organization to share the workspace with any authenticated user." | ||
checked={!settings?.workspaceSharingDisabled} | ||
onChange={(checked) => handleUpdateTeamSettings({ workspaceSharingDisabled: !checked })} | ||
disabled={isLoading || !org?.isOwner} | ||
/> | ||
|
||
<Heading2 className="pt-12">Workspace Settings</Heading2> | ||
<TextInputField | ||
label="Default Image" | ||
// TODO: Provide document links | ||
hint="Use any official Gitpod Docker image, or Docker image reference" | ||
value={defaultWorkspaceImage} | ||
onChange={setDefaultWorkspaceImage} | ||
disabled={isLoading || !org?.isOwner} | ||
/> | ||
Comment on lines
+240
to
+247
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue(non-blocking): Minor UX issue, but could we use a placeholder for now when someone is deleting the text input_ here? Feels a bit unintuitive when you delete all text and click save that there's a default value overriding the empty text input. Could be improved later. |
||
|
||
{org?.isOwner && ( | ||
<Button htmlType="submit" className="mt-4" disabled={!org.isOwner}> | ||
Update Default Image | ||
</Button> | ||
)} | ||
</form> | ||
); | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are effectively reimplementing the permissions here in an implicit way. Instead of assuming that owners have access we should rely on FGA to figure that out. Not necessarily as part of this PR, though because it involves more discussion and work.
cc @akosyakov @geropl