-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
Copy pathWorkspaces.tsx
195 lines (184 loc) · 9.55 KB
/
Workspaces.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
/**
* 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 React from "react";
import { WhitelistedRepository, Workspace, WorkspaceInfo } from "@gitpod/gitpod-protocol";
import Header from "../components/Header";
import DropDown from "../components/DropDown"
import { WorkspaceModel } from "./workspace-model";
import { WorkspaceEntry } from "./WorkspaceEntry";
import { getGitpodService, gitpodHostUrl } from "../service/service";
import {StartWorkspaceModal, WsStartEntry} from "./StartWorkspaceModal";
export interface WorkspacesProps {
}
export interface WorkspacesState {
workspaces: WorkspaceInfo[];
isTemplateModelOpen: boolean;
repos: WhitelistedRepository[];
}
export class Workspaces extends React.Component<WorkspacesProps, WorkspacesState> {
protected workspaceModel: WorkspaceModel | undefined;
constructor(props: WorkspacesProps) {
super(props);
this.state = {
workspaces: [],
isTemplateModelOpen: false,
repos: [],
};
}
async componentDidMount() {
this.workspaceModel = new WorkspaceModel(this.setWorkspaces);
const repos = await getGitpodService().server.getFeaturedRepositories();
this.setState({
repos
})
}
protected setWorkspaces = (workspaces: WorkspaceInfo[]) => {
this.setState({
workspaces
});
}
render() {
const wsModel = this.workspaceModel;
const toggleStartWSModal = () => this.setState({
isTemplateModelOpen: !this.state?.isTemplateModelOpen
});
const onActive = () => wsModel!.active = true;
const onAll = () => wsModel!.active = false;
return <>
<Header title="Workspaces" subtitle="Manage recent and stopped workspaces." />
<div className="lg:px-28 px-10 pt-8 flex">
<div className="flex">
<div className="py-4">
<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd" d="M6 2a4 4 0 100 8 4 4 0 000-8zM0 6a6 6 0 1110.89 3.477l4.817 4.816a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 010 6z" fill="#A8A29E"/>
</svg>
</div>
<input className="border-0" type="text" placeholder="Search Workspaces" onChange={(v) => { if (wsModel) wsModel.setSearch(v.target.value) }} />
</div>
<div className="flex-1" />
<div className="py-3">
<DropDown prefix="Filter: " contextMenuWidth="w-32" entries={[{
title: 'Active',
onClick: onActive
}, {
title: 'All',
onClick: onAll
}]} />
</div>
<div className="py-3 pl-3">
<DropDown prefix="Limit: " contextMenuWidth="w-32" entries={[{
title: '50',
onClick: () => { if (wsModel) wsModel.limit = 50; }
}, {
title: '100',
onClick: () => { if (wsModel) wsModel.limit = 100; }
}, {
title: '200',
onClick: () => { if (wsModel) wsModel.limit = 200; }
}]} />
</div>
{wsModel && this.state?.workspaces.length > 0 ?
<button onClick={toggleStartWSModal} className="ml-2 font-medium">New Workspace</button>
: null
}
</div>
{wsModel && (
this.state?.workspaces.length > 0 || wsModel.searchTerm ?
<div className="lg:px-28 px-10 flex flex-col space-y-2">
<div className="px-6 py-3 flex justify-between space-x-2 text-sm text-gray-400 border-t border-b border-gray-200">
<div className="w-6"></div>
<div className="w-3/12">Name</div>
<div className="w-4/12">Context</div>
<div className="w-2/12">Pending Changes</div>
<div className="w-2/12">Last Start</div>
<div className="w-8"></div>
</div>
{
wsModel.active || wsModel.searchTerm ? null :
<div className="whitespace-nowrap flex space-x-2 py-6 px-6 w-full justify-between bg-gitpod-kumquat-light rounded-xl">
<div className="pr-3 self-center w-6">
<div className="relative rounded-full w-3 h-3 bg-red">
<div className="absolute text-xs text-gray-100"
style={{
left: "5px",
top: "-2px"
}}
ref={(node) => {
if (node) {
node.style.setProperty("font-size", "10px", "important");
}
}}>!</div>
</div>
</div>
<div className="flex-1 flex flex-col overflow-x-auto">
<div className="text-red font-semibold">Garbage Collection</div>
<p className="text-gray-500">Unpinned workspaces that have been stopped for more than 14 days will be automatically deleted. <a className="text-blue-600 underline underline-thickness-thin underline-offset-small hover:text-gray-800" href="https://www.gitpod.io/docs/life-of-workspace/#garbage-collection">Learn more</a></p>
</div>
</div>
}
{
this.state?.workspaces.map(e => {
return <WorkspaceEntry key={e.workspace.id} desc={e} model={wsModel} />
})
}
</div>
:
<div className="lg:px-28 px-10 flex flex-col space-y-2">
<div className="px-6 py-3 flex justify-between space-x-2 text-sm text-gray-400 border-t border-gray-200 h-96">
<div className="flex flex-col items-center w-96 m-auto">
<h3 className="text-center pb-3">No Active Workspaces</h3>
<div className="text-center pb-6 text-gray-500">Prefix any git repository URL with gitpod.io/# or start a new workspace for a recently used project. <a className="text-gray-400 underline underline-thickness-thin underline-offset-small hover:text-gray-600" href="https://www.gitpod.io/docs/getting-started/">Learn more</a></div>
<button onClick={toggleStartWSModal} className="font-medium">New Workspace</button>
</div>
</div>
</div>
)}
<StartWorkspaceModal
onClose={toggleStartWSModal}
visible={!!this.state?.isTemplateModelOpen}
examples={this.state?.repos && this.state.repos.map(r => ({
title: r.name,
description: r.description || r.url,
startUrl: gitpodHostUrl.withContext(r.url).toString()
}))}
recent={wsModel && this.state?.workspaces ?
this.getRecentSuggestions()
: []} />
</>;
}
protected getRecentSuggestions(): WsStartEntry[] {
if (this.workspaceModel) {
const all = this.workspaceModel.getAllFetchedWorkspaces();
if (all && all.size > 0) {
const index = new Map<string, WsStartEntry & {lastUse: string}>();
for (const ws of Array.from(all.values())) {
const repoUrl = Workspace.getFullRepositoryUrl(ws.workspace);
if (repoUrl) {
const lastUse = WorkspaceInfo.lastActiveISODate(ws);
let entry = index.get(repoUrl);
if (!entry) {
entry = {
title: Workspace.getFullRepositoryName(ws.workspace) || repoUrl,
description: repoUrl,
startUrl: gitpodHostUrl.withContext(repoUrl).toString(),
lastUse,
};
index.set(repoUrl, entry);
} else {
if (entry.lastUse.localeCompare(lastUse) < 0) {
entry.lastUse = lastUse;
}
}
}
}
const list = Array.from(index.values());
list.sort((a,b) => b.lastUse.localeCompare(a.lastUse));
return list;
}
}
return [];
}
}