Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/compute/compute-server-log.tsx
1503 views
1
/*
2
Show the log entries for a specific compute server.
3
4
More precisely this is a little button that you click on, and
5
it shows the log in a modal.
6
*/
7
8
import {
9
Modal,
10
Button,
11
Checkbox,
12
Flex,
13
Radio,
14
Spin,
15
Table,
16
Tooltip,
17
} from "antd";
18
import { useEffect, useState } from "react";
19
import LogEntry from "./log-entry";
20
import type { ComputeServerEvent } from "@cocalc/util/compute/log";
21
import { TimeAgo } from "@cocalc/frontend/components";
22
import { getLog } from "./api";
23
import { Icon } from "@cocalc/frontend/components";
24
import getTitle from "./get-title";
25
import { getPurchases } from "@cocalc/frontend/purchases/api";
26
import type { Purchase } from "@cocalc/util/db-schema/purchases";
27
import ShowError from "@cocalc/frontend/components/error";
28
import {
29
DetailedPurchaseTable,
30
GroupedPurchaseTable,
31
} from "@cocalc/frontend/purchases/purchases";
32
import { currency } from "@cocalc/util/misc";
33
34
export default function ComputeServerLog({
35
id,
36
style,
37
}: {
38
id: number;
39
style?;
40
color?: string;
41
}) {
42
const [open, setOpen] = useState<boolean>(false);
43
44
return (
45
<Tooltip title={"Show configuration and control log"}>
46
<Button
47
size={"small"}
48
type="text"
49
style={{ color: "#666", ...style }}
50
onClick={() => {
51
setOpen(true);
52
}}
53
>
54
<Icon name="history" /> Log
55
</Button>
56
{open && <LogModal id={id} close={() => setOpen(false)} />}
57
</Tooltip>
58
);
59
}
60
61
const OPTIONS = [
62
//{ label: "Files", value: "files" },
63
{ label: "Activity", value: "activity" },
64
{ label: "Purchases", value: "purchases" },
65
];
66
67
const LIMIT = 500;
68
69
export function LogModal({ id, close }) {
70
const [log, setLog] = useState<
71
| null
72
| any[]
73
| { time: Date; project_id: string; event: ComputeServerEvent }[]
74
| Purchase[]
75
>(null);
76
const [title, setTitle] = useState<string>("");
77
const [type, setType] = useState<string>("activity");
78
const [error, setError] = useState<string>("");
79
const [total, setTotal] = useState<number | null>(null);
80
const [group, setGroup] = useState<boolean>(false);
81
82
useEffect(() => {
83
(async () => {
84
setTitle((await getTitle(id)).title);
85
})();
86
}, [id]);
87
88
useEffect(() => {
89
(async () => {
90
try {
91
if (type == "files" || type == "activity") {
92
setLog(await getLog({ id, type }));
93
} else if (type == "purchases") {
94
const { purchases } = await getPurchases({
95
compute_server_id: id,
96
limit: LIMIT,
97
group,
98
});
99
setLog(purchases);
100
let total = 0;
101
for (const { cost, cost_so_far } of purchases) {
102
total += cost ?? cost_so_far ?? 0;
103
}
104
setTotal(total);
105
}
106
} catch (err) {
107
setError(`${err}`);
108
}
109
})();
110
}, [type, id, group]);
111
112
return (
113
<Modal
114
width={1100}
115
title={
116
<>
117
<Icon name="server" /> Compute Server Log - "{title}"
118
</>
119
}
120
open
121
onCancel={close}
122
onOk={close}
123
>
124
<Flex style={{ marginBottom: "15px" }}>
125
<Radio.Group
126
options={OPTIONS}
127
onChange={({ target: { value } }) => {
128
setLog(null);
129
setTotal(null);
130
setType(value);
131
}}
132
value={type}
133
optionType="button"
134
/>
135
<div style={{ flex: 1 }} />
136
{type == "purchases" && (
137
<Checkbox
138
style={{ alignItems: "center" }}
139
checked={group}
140
onChange={(e) => {
141
setGroup(e.target.checked);
142
setLog(null);
143
setTotal(null);
144
}}
145
>
146
Group by Service
147
</Checkbox>
148
)}
149
<div style={{ flex: 1 }} />
150
{total != null && log != null && (
151
<div>
152
<b>
153
Total{" "}
154
{log.length <= LIMIT ? "Spend on Server" : "of Displayed Spend"}:{" "}
155
{currency(total)}
156
</b>
157
</div>
158
)}
159
</Flex>
160
<ShowError
161
error={error}
162
setError={setError}
163
style={{ margin: "15px 0" }}
164
/>
165
{log == null && (
166
<div style={{ textAlign: "center", margin: "15px" }}>
167
<Spin />
168
</div>
169
)}
170
{log != null && type == "purchases" && !group && (
171
<DetailedPurchaseTable
172
purchases={log}
173
hideColumns={new Set(["project"])}
174
style={{ maxHeight: "70vh", overflow: "auto" }}
175
/>
176
)}
177
{log != null && type == "purchases" && group && (
178
<GroupedPurchaseTable
179
purchases={log}
180
hideColumns={new Set(["project"])}
181
style={{ maxHeight: "70vh", overflow: "auto" }}
182
/>
183
)}
184
{log != null && type == "activity" && (
185
<Table
186
dataSource={log}
187
rowKey={(record) => record.id}
188
pagination={false}
189
style={{ maxHeight: "70vh", overflow: "auto" }}
190
>
191
<Table.Column
192
title={"Log Entry"}
193
render={(record) => {
194
return (
195
<LogEntry
196
event={record.event}
197
hideTitle
198
project_id={record.project_id}
199
/>
200
);
201
}}
202
/>
203
<Table.Column
204
title="Time"
205
render={(record) => <TimeAgo date={record.time} />}
206
/>
207
</Table>
208
)}
209
</Modal>
210
);
211
}
212
213