CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/account/ssh-keys/ssh-key-list.tsx
Views: 687
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, 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
22
interface SSHKeyListProps {
23
ssh_keys?: Map<string, any>;
24
project_id?: string;
25
help?: 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 const SSHKeyList: React.FC<SSHKeyListProps> = (
33
props: SSHKeyListProps,
34
) => {
35
const intl = useIntl();
36
const { ssh_keys, project_id, help, children, mode = "project" } = props;
37
const isFlyout = mode === "flyout";
38
39
function render_header() {
40
return (
41
<>
42
{intl.formatMessage(labels.ssh_keys)} <Gap />
43
{help && <HelpIcon title="Using SSH Keys">{help}</HelpIcon>}
44
</>
45
);
46
}
47
48
function render_keys() {
49
if (ssh_keys == null || ssh_keys.size == 0) return;
50
const v: { date?: Date; fp: string; component: JSX.Element }[] = [];
51
52
ssh_keys?.forEach(
53
(ssh_key: Map<string, any>, fingerprint: string): void => {
54
if (!ssh_key) {
55
return;
56
}
57
ssh_key = ssh_key.set("fingerprint", fingerprint);
58
v.push({
59
date: ssh_key.get("last_use_date"),
60
fp: fingerprint,
61
component: (
62
<OneSSHKey
63
ssh_key={ssh_key}
64
key={fingerprint}
65
project_id={project_id}
66
mode={mode}
67
/>
68
),
69
});
70
},
71
);
72
// sort in reverse order by last_use_date, then by fingerprint
73
v.sort(function (a, b) {
74
if (a.date != null && b.date != null) {
75
return -cmp(a.date, b.date);
76
}
77
if (a.date && b.date == null) {
78
return -1;
79
}
80
if (b.date && a.date == null) {
81
return +1;
82
}
83
return cmp(a.fp, b.fp);
84
});
85
if (isFlyout) {
86
return <div>{v.map((x) => x.component)}</div>;
87
} else {
88
return (
89
<SettingBox style={{ marginBottom: "0px" }} show_header={false}>
90
{v.map((x) => x.component)}
91
</SettingBox>
92
);
93
}
94
}
95
96
function renderBody() {
97
return (
98
<>
99
{children}
100
{render_keys()}
101
</>
102
);
103
}
104
105
if (isFlyout) {
106
return renderBody();
107
} else {
108
return (
109
<SettingBox title={render_header()} icon={"list-ul"}>
110
{renderBody()}
111
</SettingBox>
112
);
113
}
114
};
115
116
interface OneSSHKeyProps {
117
ssh_key: Map<string, any>;
118
project_id?: string;
119
mode?: "project" | "flyout";
120
}
121
122
function OneSSHKey({ ssh_key, project_id, mode = "project" }: OneSSHKeyProps) {
123
const isFlyout = mode === "flyout";
124
125
function render_last_use(): JSX.Element {
126
const d = ssh_key.get("last_use_date");
127
if (d) {
128
return (
129
<span style={{ color: "#1e7e34" }}>
130
Last used <TimeAgo date={new Date(d)} />
131
</span>
132
);
133
} else {
134
return <span style={{ color: "#333" }}>Never used</span>;
135
}
136
}
137
138
function delete_key(): void {
139
const fingerprint = ssh_key.get("fingerprint");
140
if (project_id) {
141
redux.getActions("projects").delete_ssh_key_from_project({
142
fingerprint,
143
project_id: project_id,
144
});
145
} else {
146
redux.getActions("account").delete_ssh_key(fingerprint);
147
}
148
}
149
150
const key_style: React.CSSProperties = {
151
fontSize: isFlyout ? "42px" : "72px",
152
color: ssh_key.get("last_use_date") ? "#1e7e34" : "#888",
153
};
154
155
return (
156
<div
157
style={{
158
display: "flex",
159
borderBottom: "1px solid #ccc",
160
padding: "15px",
161
}}
162
>
163
<div style={{ width: isFlyout ? "48px" : "100px", display: "flex" }}>
164
<Icon style={key_style} name="key" />
165
</div>
166
<div style={{ flex: 1 }}>
167
<Popconfirm
168
title={
169
<div>
170
Are you sure you want to delete this SSH key? <br />
171
This CANNOT be undone. <br /> If you want to reuse this key in the
172
future, you will have to upload it again.
173
</div>
174
}
175
onConfirm={() => delete_key()}
176
okText={"Yes, delete key"}
177
cancelText={<CancelText />}
178
>
179
<Button
180
size={isFlyout ? "small" : "middle"}
181
style={{ float: "right" }}
182
>
183
<Icon name="trash" /> Delete...
184
</Button>
185
</Popconfirm>
186
<div style={{ fontWeight: 600 }}>{ssh_key.get("title")}</div>
187
<span style={{ fontWeight: 600 }}>Fingerprint: </span>
188
<Typography.Text code style={{ fontSize: "80%" }}>
189
{ssh_key.get("fingerprint")}
190
</Typography.Text>
191
<br />
192
Added on {new Date(ssh_key.get("creation_date")).toLocaleDateString()}
193
<div> {render_last_use()} (NOTE: not all usage is tracked.)</div>
194
</div>
195
</div>
196
);
197
}
198
199