Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/account/ssh-keys/ssh-key-list.tsx
1503 views
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { Button, Flex, Popconfirm, Typography } from "antd";
7
import { Map } from "immutable";
8
import { useIntl } from "react-intl";
9
import { redux } from "@cocalc/frontend/app-framework";
10
import {
11
Gap,
12
HelpIcon,
13
Icon,
14
SettingBox,
15
TimeAgo,
16
} from "@cocalc/frontend/components";
17
import { labels } from "@cocalc/frontend/i18n";
18
import { CancelText } from "@cocalc/frontend/i18n/components";
19
import { cmp } from "@cocalc/util/misc";
20
import SSHKeyAdder from "./ssh-key-adder";
21
22
interface SSHKeyListProps {
23
ssh_keys?: Map<string, any>;
24
project_id?: string;
25
help?: React.JSX.Element;
26
children?: any;
27
mode?: "project" | "flyout";
28
}
29
30
// Children are rendered above the list of SSH Keys
31
// Takes an optional Help string or node to render as a help modal
32
export default function SSHKeyList({
33
ssh_keys,
34
project_id,
35
help,
36
children,
37
mode = "project",
38
}: SSHKeyListProps) {
39
const intl = useIntl();
40
const isFlyout = mode === "flyout";
41
42
function renderAdder(size?) {
43
if (project_id) {
44
return (
45
<SSHKeyAdder
46
size={size}
47
add_ssh_key={(opts) => {
48
redux
49
.getActions("projects")
50
.add_ssh_key_to_project({ ...opts, project_id });
51
}}
52
style={{ marginBottom: "10px" }}
53
extra={
54
<p>
55
If you want to use the same SSH key for all your projects and
56
compute servers, add it using the "SSH Keys" tab under Account
57
Settings. If you have done that, there is no need to also
58
configure an SSH key here.
59
</p>
60
}
61
/>
62
);
63
} else {
64
return (
65
<SSHKeyAdder
66
size={size}
67
add_ssh_key={(opts) => redux.getActions("account").add_ssh_key(opts)}
68
style={{ marginBottom: "0px" }}
69
/>
70
);
71
}
72
}
73
74
function render_header() {
75
return (
76
<Flex style={{ width: "100%" }}>
77
{project_id ? "Project Specific " : "Global "}
78
{intl.formatMessage(labels.ssh_keys)} <Gap />
79
{help && <HelpIcon title="Using SSH Keys">{help}</HelpIcon>}
80
<div style={{ flex: 1 }} />
81
{(ssh_keys?.size ?? 0) > 0 ? (
82
<div style={{ float: "right" }}>{renderAdder()}</div>
83
) : undefined}
84
</Flex>
85
);
86
}
87
88
function render_keys() {
89
if (ssh_keys == null || ssh_keys.size == 0) {
90
return <div style={{ textAlign: "center" }}>{renderAdder("large")}</div>;
91
}
92
const v: { date?: Date; fp: string; component: React.JSX.Element }[] = [];
93
94
ssh_keys?.forEach(
95
(ssh_key: Map<string, any>, fingerprint: string): void => {
96
if (!ssh_key) {
97
return;
98
}
99
ssh_key = ssh_key.set("fingerprint", fingerprint);
100
v.push({
101
date: ssh_key.get("last_use_date"),
102
fp: fingerprint,
103
component: (
104
<OneSSHKey
105
ssh_key={ssh_key}
106
key={fingerprint}
107
project_id={project_id}
108
mode={mode}
109
/>
110
),
111
});
112
},
113
);
114
// sort in reverse order by last_use_date, then by fingerprint
115
v.sort(function (a, b) {
116
if (a.date != null && b.date != null) {
117
return -cmp(a.date, b.date);
118
}
119
if (a.date && b.date == null) {
120
return -1;
121
}
122
if (b.date && a.date == null) {
123
return +1;
124
}
125
return cmp(a.fp, b.fp);
126
});
127
if (isFlyout) {
128
return <div>{v.map((x) => x.component)}</div>;
129
} else {
130
return (
131
<SettingBox style={{ marginBottom: "0px" }} show_header={false}>
132
{v.map((x) => x.component)}
133
</SettingBox>
134
);
135
}
136
}
137
138
function renderBody() {
139
return (
140
<>
141
{children}
142
{render_keys()}
143
</>
144
);
145
}
146
147
if (isFlyout) {
148
return renderBody();
149
} else {
150
return (
151
<SettingBox title={render_header()} icon={"list-ul"}>
152
{renderBody()}
153
</SettingBox>
154
);
155
}
156
}
157
158
interface OneSSHKeyProps {
159
ssh_key: Map<string, any>;
160
project_id?: string;
161
mode?: "project" | "flyout";
162
}
163
164
function OneSSHKey({ ssh_key, project_id, mode = "project" }: OneSSHKeyProps) {
165
const isFlyout = mode === "flyout";
166
167
function render_last_use(): React.JSX.Element {
168
const d = ssh_key.get("last_use_date");
169
if (d) {
170
return (
171
<span style={{ color: "#1e7e34" }}>
172
Last used <TimeAgo date={new Date(d)} />
173
</span>
174
);
175
} else {
176
return <span style={{ color: "#333" }}>Never used</span>;
177
}
178
}
179
180
function delete_key(): void {
181
const fingerprint = ssh_key.get("fingerprint");
182
if (project_id) {
183
redux.getActions("projects").delete_ssh_key_from_project({
184
fingerprint,
185
project_id: project_id,
186
});
187
} else {
188
redux.getActions("account").delete_ssh_key(fingerprint);
189
}
190
}
191
192
const key_style: React.CSSProperties = {
193
fontSize: isFlyout ? "42px" : "72px",
194
color: ssh_key.get("last_use_date") ? "#1e7e34" : "#888",
195
};
196
197
return (
198
<div
199
style={{
200
display: "flex",
201
borderBottom: "1px solid #ccc",
202
padding: "15px",
203
}}
204
>
205
<div style={{ width: isFlyout ? "48px" : "100px", display: "flex" }}>
206
<Icon style={key_style} name="key" />
207
</div>
208
<div style={{ flex: 1 }}>
209
<Popconfirm
210
title={
211
<div>
212
Are you sure you want to delete this SSH key? <br />
213
This CANNOT be undone. <br /> If you want to reuse this key in the
214
future, you will have to upload it again.
215
</div>
216
}
217
onConfirm={() => delete_key()}
218
okText={"Yes, delete key"}
219
cancelText={<CancelText />}
220
>
221
<Button
222
type="link"
223
size={isFlyout ? "small" : "middle"}
224
style={{ float: "right" }}
225
>
226
<Icon name="trash" /> Delete...
227
</Button>
228
</Popconfirm>
229
<div style={{ fontWeight: 600 }}>{ssh_key.get("title")}</div>
230
<span style={{ fontWeight: 600 }}>Fingerprint: </span>
231
<Typography.Text code style={{ fontSize: "80%" }}>
232
{ssh_key.get("fingerprint")}
233
</Typography.Text>
234
<br />
235
Added on {new Date(ssh_key.get("creation_date")).toLocaleDateString()}
236
<div> {render_last_use()} (NOTE: not all usage is tracked.)</div>
237
</div>
238
</div>
239
);
240
}
241
242