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