diff --git a/components/dashboard/src/components/Arrow.tsx b/components/dashboard/src/components/Arrow.tsx index ff1132afc53798..7ac5b282516042 100644 --- a/components/dashboard/src/components/Arrow.tsx +++ b/components/dashboard/src/components/Arrow.tsx @@ -10,7 +10,7 @@ function Arrow(props: { up: boolean; customBorderClasses?: string }) { className={ "mx-2 " + (props.customBorderClasses || - "border-gray-400 dark:border-gray-600 group-hover:border-gray-600 dark:group-hover:border-gray-400") + "border-gray-400 dark:border-gray-500 group-hover:border-gray-600 dark:group-hover:border-gray-400") } style={{ marginTop: 2, diff --git a/components/dashboard/src/workspaces/Workspaces.tsx b/components/dashboard/src/workspaces/Workspaces.tsx index ccd1b7ea45f202..c3cfd8a1bd2931 100644 --- a/components/dashboard/src/workspaces/Workspaces.tsx +++ b/components/dashboard/src/workspaces/Workspaces.tsx @@ -18,6 +18,8 @@ import { User } from "@gitpod/gitpod-protocol"; import { useLocation } from "react-router"; import { StartWorkspaceModalContext, StartWorkspaceModalKeyBinding } from "./start-workspace-modal-context"; import SelectIDEModal from "../settings/SelectIDEModal"; +import Arrow from "../components/Arrow"; +import ConfirmationModal from "../components/ConfirmationModal"; export interface WorkspacesProps {} @@ -25,6 +27,8 @@ export interface WorkspacesState { workspaces: WorkspaceInfo[]; isTemplateModelOpen: boolean; repos: WhitelistedRepository[]; + showInactive: boolean; + deleteModalVisible: boolean; } export default function () { @@ -35,6 +39,8 @@ export default function () { const [activeWorkspaces, setActiveWorkspaces] = useState([]); const [inactiveWorkspaces, setInactiveWorkspaces] = useState([]); const [workspaceModel, setWorkspaceModel] = useState(); + const [showInactive, setShowInactive] = useState(); + const [deleteModalVisible, setDeleteModalVisible] = useState(); const { setIsStartWorkspaceModalVisible } = useContext(StartWorkspaceModalContext); useEffect(() => { @@ -50,6 +56,18 @@ export default function () { <>
+ setDeleteModalVisible(false)} + onConfirm={() => { + inactiveWorkspaces.forEach((ws) => workspaceModel?.deleteWorkspace(ws.workspace.id)); + setDeleteModalVisible(false); + }} + > + {isOnboardingUser && } {workspaceModel?.initialized && @@ -128,27 +146,65 @@ export default function () { })} {activeWorkspaces.length > 0 &&
} {inactiveWorkspaces.length > 0 && ( -
- Unpinned workspaces that have been inactive for more than 14 days will be - automatically deleted.{" "} - +
setShowInactive(!showInactive)} + className="flex cursor-pointer py-6 px-6 flex-row text-gray-400 bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 rounded-xl mb-2" > - Learn more - +
+ +
+
+
+ Inactive Workspaces  + + {inactiveWorkspaces.length} + +
+
+ Unpinned workspaces that have been inactive for more than 14 days will + be automatically deleted.{" "} + evt.stopPropagation()} + > + Learn more + +
+
+
+ {showInactive ? ( + + ) : null} +
+
+ {showInactive ? ( + <> + {inactiveWorkspaces.map((e) => { + return ( + + getGitpodService().server.stopWorkspace(wsId) + } + /> + ); + })} + + ) : null}
)} - {inactiveWorkspaces.map((e) => { - return ( - getGitpodService().server.stopWorkspace(wsId)} - /> - ); - })} ) : ( diff --git a/components/dashboard/src/workspaces/workspace-model.ts b/components/dashboard/src/workspaces/workspace-model.ts index f08aecd929e62c..3d6ec2c419a6c5 100644 --- a/components/dashboard/src/workspaces/workspace-model.ts +++ b/components/dashboard/src/workspaces/workspace-model.ts @@ -11,6 +11,7 @@ import { WorkspaceInfo, WorkspaceInstance, } from "@gitpod/gitpod-protocol"; +import { hoursBefore, isDateSmallerOrEqual } from "@gitpod/gitpod-protocol/lib/util/timeutil"; import { getGitpodService } from "../service/service"; export class WorkspaceModel implements Disposable, Partial { @@ -145,8 +146,12 @@ export class WorkspaceModel implements Disposable, Partial { } protected isActive(info: WorkspaceInfo): boolean { + const lastSessionStart = WorkspaceInfo.lastActiveISODate(info); + const twentyfourHoursAgo = hoursBefore(new Date().toISOString(), 24); return ( - (info.workspace.pinned || (!!info.latestInstance && info.latestInstance.status?.phase !== "stopped")) && + (info.workspace.pinned || + (!!info.latestInstance && info.latestInstance.status?.phase !== "stopped") || + isDateSmallerOrEqual(twentyfourHoursAgo, lastSessionStart)) && !info.workspace.softDeleted ); } diff --git a/components/gitpod-protocol/src/util/timeutil.ts b/components/gitpod-protocol/src/util/timeutil.ts index ca54246b1c9646..9f0971b46d8832 100644 --- a/components/gitpod-protocol/src/util/timeutil.ts +++ b/components/gitpod-protocol/src/util/timeutil.ts @@ -47,6 +47,12 @@ export const orderAsc = (d1: string, d2: string): number => liftDate(d1, d2, (d1 export const liftDate1 = (d1: string, f: (d1: Date) => T): T => f(new Date(d1)); export const liftDate = (d1: string, d2: string, f: (d1: Date, d2: Date) => T): T => f(new Date(d1), new Date(d2)); +export function hoursBefore(date: string, hours: number): string { + const result = new Date(date); + result.setHours(result.getHours() - hours); + return result.toISOString(); +} + export function hoursLater(date: string, hours: number): string { const result = new Date(date); result.setHours(result.getHours() + hours);