diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ac9a2c48e3463..76a42c9d2b551d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. ## June 2021 +- Adding `ItemsList` component as a more maintainable and consistent way to render a list of workspaces, git integrations, environment variables, etc. ([#4454](https://github.com/gitpod-io/gitpod/pull/4454)) - Improve backup stability when pods get evicted ([#4405](https://github.com/gitpod-io/gitpod/pull/4405)) - Fix text color in workspaces list for dark theme ([#4410](https://github.com/gitpod-io/gitpod/pull/4410)) - Better reflect incremental prebuilds in prebuilt workspace logs ([#4293](https://github.com/gitpod-io/gitpod/pull/4293)) diff --git a/components/dashboard/src/components/ContextMenu.tsx b/components/dashboard/src/components/ContextMenu.tsx index abd735000e1565..b4b1d4935b1657 100644 --- a/components/dashboard/src/components/ContextMenu.tsx +++ b/components/dashboard/src/components/ContextMenu.tsx @@ -8,7 +8,7 @@ import { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; export interface ContextMenuProps { - children: React.ReactChild[] | React.ReactChild; + children?: React.ReactChild[] | React.ReactChild; menuEntries: ContextMenuEntry[]; width?: string; } @@ -57,13 +57,16 @@ function ContextMenu(props: ContextMenuProps) { const menuId = String(Math.random()); + // Default 'children' is the three dots hamburger button. + const children = props.children || Actions; + return (
{ toggleExpanded(); e.stopPropagation(); }}> - {props.children} + {children}
{expanded ?
diff --git a/components/dashboard/src/components/ItemsList.tsx b/components/dashboard/src/components/ItemsList.tsx new file mode 100644 index 00000000000000..dc58ba532cfda0 --- /dev/null +++ b/components/dashboard/src/components/ItemsList.tsx @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2021 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import ContextMenu, { ContextMenuEntry } from "./ContextMenu" + +export function ItemsList(props: { + children?: React.ReactNode; + className?: string; +}) { + return
+ {props.children} +
; +} + +export function Item(props: { + children?: React.ReactNode; + className?: string; + header?: boolean; +}) { + const headerClassName = "text-sm text-gray-400 border-t border-b border-gray-200 dark:border-gray-800"; + const notHeaderClassName = "rounded-xl hover:bg-gray-100 dark:hover:bg-gray-800 focus:bg-gitpod-kumquat-light"; + return
+ {props.children} +
; +} + +export function ItemField(props: { + children?: React.ReactNode; + className?: string; +}) { + return
+ {props.children} +
; +} + +export function ItemFieldIcon(props: { + children?: React.ReactNode; + className?: string; +}) { + return
+ {props.children} +
; +} + +export function ItemFieldContextMenu(props: { + menuEntries?: ContextMenuEntry[]; + className?: string; +}) { + return
+ {!props.menuEntries ? null : } +
; +} diff --git a/components/dashboard/src/settings/EnvironmentVariables.tsx b/components/dashboard/src/settings/EnvironmentVariables.tsx index a3e546c9c4d1c5..521f299983f19c 100644 --- a/components/dashboard/src/settings/EnvironmentVariables.tsx +++ b/components/dashboard/src/settings/EnvironmentVariables.tsx @@ -5,13 +5,13 @@ */ import { UserEnvVarValue } from "@gitpod/gitpod-protocol"; -import { useEffect, useRef, useState } from "react"; -import ContextMenu from "../components/ContextMenu"; +import React, { useEffect, useRef, useState } from "react"; +import ConfirmationModal from "../components/ConfirmationModal"; +import { Item, ItemField, ItemFieldContextMenu, ItemsList } from "../components/ItemsList"; import Modal from "../components/Modal"; -import { getGitpodService } from "../service/service"; import { PageWithSubMenu } from "../components/PageWithSubMenu"; +import { getGitpodService } from "../service/service"; import settingsMenu from "./settings-menu"; -import ConfirmationModal from "../components/ConfirmationModal"; interface EnvVarModalProps { envVar: UserEnvVarValue; @@ -180,6 +180,8 @@ export default function EnvVars() { return ''; }; + + return {isAddEnvVarModalVisible && New Variable
- :
-
-
-
Name
-
Scope
-
-
-
-
- {envVars.map(variable => { - return
-
{variable.name}
-
{variable.repositoryPattern}
-
-
- edit(variable), - separator: true - }, - { - title: 'Delete', - customFontStyle: 'text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300', - onClick: () => confirmDeleteVariable(variable) - }, - ]}> - Actions - -
-
-
- })} -
-
+ : + + Name + Scope + + + {envVars.map(variable => { + return + {variable.name} + {variable.repositoryPattern} + edit(variable), + separator: true + }, + { + title: 'Delete', + customFontStyle: 'text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300', + onClick: () => confirmDeleteVariable(variable) + }, + ]} /> + + })} + } ; } \ No newline at end of file diff --git a/components/dashboard/src/settings/Integrations.tsx b/components/dashboard/src/settings/Integrations.tsx index 21624aa2238429..56612383b52506 100644 --- a/components/dashboard/src/settings/Integrations.tsx +++ b/components/dashboard/src/settings/Integrations.tsx @@ -7,19 +7,20 @@ import { AuthProviderEntry, AuthProviderInfo } from "@gitpod/gitpod-protocol"; import { SelectAccountPayload } from "@gitpod/gitpod-protocol/lib/auth"; import React, { useContext, useEffect, useState } from "react"; -import ContextMenu, { ContextMenuEntry } from "../components/ContextMenu"; -import { getGitpodService, gitpodHostUrl } from "../service/service"; -import { UserContext } from "../user-context"; -import copy from '../images/copy.svg'; -import exclamation from '../images/exclamation.svg'; import AlertBox from "../components/AlertBox"; -import Modal from "../components/Modal"; -import { openAuthorizeWindow } from "../provider-utils"; import CheckBox from '../components/CheckBox'; +import ConfirmationModal from "../components/ConfirmationModal"; +import { ContextMenuEntry } from "../components/ContextMenu"; +import { Item, ItemField, ItemFieldContextMenu, ItemFieldIcon, ItemsList } from "../components/ItemsList"; +import Modal from "../components/Modal"; import { PageWithSubMenu } from "../components/PageWithSubMenu"; -import settingsMenu from "./settings-menu"; +import copy from '../images/copy.svg'; +import exclamation from '../images/exclamation.svg'; +import { openAuthorizeWindow } from "../provider-utils"; +import { getGitpodService, gitpodHostUrl } from "../service/service"; +import { UserContext } from "../user-context"; import { SelectAccountModal } from "./SelectAccountModal"; -import ConfirmationModal from "../components/ConfirmationModal"; +import settingsMenu from "./settings-menu"; export default function Integrations() { @@ -280,36 +281,30 @@ function GitProviders() {

Git Providers

Manage permissions for git providers.

-
+ {authProviders && authProviders.map(ap => ( -
-
-
+ + +
 
-
-
+ + {ap.authProviderType} {ap.host} -
-
+ + {getUsername(ap.authProviderId) || "–"} Username -
-
+ + {getPermissions(ap.authProviderId)?.join(", ") || "–"} Permissions -
-
-
- - Actions - -
-
-
+ + + ))} -
+
); } @@ -399,31 +394,24 @@ function GitIntegrations() { )} -
+ {providers && providers.map(ap => ( -
- -
-
+ + +
 
-
-
- {ap.type} -
-
+ + + {ap.type} + + {ap.host} -
-
-
- - Actions - -
-
-
+ + + ))} -
+
); } diff --git a/components/dashboard/src/settings/Teams.tsx b/components/dashboard/src/settings/Teams.tsx index 3bbc72883c7944..ff6685ad890ff1 100644 --- a/components/dashboard/src/settings/Teams.tsx +++ b/components/dashboard/src/settings/Teams.tsx @@ -512,9 +512,7 @@ function AllTeams() {
- - Actions - +
diff --git a/components/dashboard/src/workspaces/WorkspaceEntry.tsx b/components/dashboard/src/workspaces/WorkspaceEntry.tsx index f0c9ac9c157f85..89faa69d7855fb 100644 --- a/components/dashboard/src/workspaces/WorkspaceEntry.tsx +++ b/components/dashboard/src/workspaces/WorkspaceEntry.tsx @@ -6,13 +6,14 @@ import { CommitContext, Workspace, WorkspaceInfo, WorkspaceInstance, WorkspaceInstancePhase } from '@gitpod/gitpod-protocol'; import { GitpodHostUrl } from '@gitpod/gitpod-protocol/lib/util/gitpod-host-url'; -import ContextMenu, { ContextMenuEntry } from '../components/ContextMenu'; import moment from 'moment'; -import { useState } from 'react'; -import { WorkspaceModel } from './workspace-model'; +import React, { useState } from 'react'; +import ConfirmationModal from '../components/ConfirmationModal'; +import { ContextMenuEntry } from '../components/ContextMenu'; +import { Item, ItemField, ItemFieldContextMenu, ItemFieldIcon } from '../components/ItemsList'; import PendingChangesDropdown from '../components/PendingChangesDropdown'; import Tooltip from '../components/Tooltip'; -import ConfirmationModal from '../components/ConfirmationModal'; +import { WorkspaceModel } from './workspace-model'; function getLabel(state: WorkspaceInstancePhase) { return state.substr(0,1).toLocaleUpperCase() + state.substr(1); @@ -80,38 +81,31 @@ export function WorkspaceEntry({ desc, model, isAdmin, stopWorkspace }: Props) { ); } const project = getProject(ws); - return
-
-
- -
-
-
{ws.id}
-
{project || 'Unknown'}
-
-
-
-
{ws.description}
- -
{ws.contextURL}
-
-
-
-
-
{currentBranch}
- -
-
- -
{moment(WorkspaceInfo.lastActiveISODate(desc)).fromNow()}
-
-
-
- - Actions - -
-
+ + return + + + + +
{ws.id}
+
{project || 'Unknown'}
+
+ +
{ws.description}
+ +
{ws.contextURL}
+
+
+ +
{currentBranch}
+ +
+ + +
{moment(WorkspaceInfo.lastActiveISODate(desc)).fromNow()}
+
+
+ {isModalVisible && setModalVisible(false)} onConfirm={() => model.deleteWorkspace(ws.id)} />} -
; + ; } export function getProject(ws: Workspace) { @@ -156,8 +150,9 @@ export function WorkspaceStatusIndicator({instance}: {instance?: WorkspaceInstan break; } } - return -
-
-
; + return
+ +
+ +
; } \ No newline at end of file diff --git a/components/dashboard/src/workspaces/Workspaces.tsx b/components/dashboard/src/workspaces/Workspaces.tsx index 025281e98bab1f..95d025385fe145 100644 --- a/components/dashboard/src/workspaces/Workspaces.tsx +++ b/components/dashboard/src/workspaces/Workspaces.tsx @@ -13,6 +13,7 @@ import { WorkspaceModel } from "./workspace-model"; import { WorkspaceEntry } from "./WorkspaceEntry"; import { getGitpodService, gitpodHostUrl } from "../service/service"; import {StartWorkspaceModal, WsStartEntry} from "./StartWorkspaceModal"; +import { Item, ItemField, ItemFieldContextMenu, ItemFieldIcon, ItemsList } from "../components/ItemsList"; export interface WorkspacesProps { } @@ -104,33 +105,33 @@ export default class Workspaces extends React.Component {wsModel && ( this.state?.workspaces.length > 0 || wsModel.searchTerm ? -
-
-
-
Name
-
Context
-
Pending Changes
-
Last Start
-
-
+ + + + Name + Context + Pending Changes + Last Start + + { wsModel.active || wsModel.searchTerm ? null : -
-
- -
-
+ + + Exclamation Mark + +
Garbage Collection

Unpinned workspaces that have been stopped for more than 14 days will be automatically deleted. Learn more

-
-
+ + } { this.state?.workspaces.map(e => { return getGitpodService().server.stopWorkspace(wsId)}/> }) } -
+ :