@rollupdate/hctl 0.0.0-stage → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1071 -2
- package/dist/auction/cache.d.ts +6 -0
- package/dist/auction/cache.js +55 -0
- package/dist/auction/client.d.ts +24 -0
- package/dist/auction/client.js +236 -0
- package/dist/auction/commands.d.ts +2 -0
- package/dist/auction/commands.js +440 -0
- package/dist/auction/diff-formatter.d.ts +3 -0
- package/dist/auction/diff-formatter.js +61 -0
- package/dist/auction/diff.d.ts +11 -0
- package/dist/auction/diff.js +28 -0
- package/dist/auction/formatter.d.ts +21 -0
- package/dist/auction/formatter.js +182 -0
- package/dist/auction/store.d.ts +24 -0
- package/dist/auction/store.js +177 -0
- package/dist/auction/types.d.ts +74 -0
- package/dist/auction/types.js +5 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +74 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +4 -0
- package/dist/cloud/browser-bootstrap.d.ts +28 -0
- package/dist/cloud/browser-bootstrap.js +480 -0
- package/dist/cloud/client.d.ts +520 -0
- package/dist/cloud/client.js +755 -0
- package/dist/cloud/commands/certificate.d.ts +2 -0
- package/dist/cloud/commands/certificate.js +108 -0
- package/dist/cloud/commands/context.d.ts +2 -0
- package/dist/cloud/commands/context.js +133 -0
- package/dist/cloud/commands/datacenter.d.ts +2 -0
- package/dist/cloud/commands/datacenter.js +24 -0
- package/dist/cloud/commands/firewall.d.ts +2 -0
- package/dist/cloud/commands/firewall.js +142 -0
- package/dist/cloud/commands/floating-ip.d.ts +2 -0
- package/dist/cloud/commands/floating-ip.js +138 -0
- package/dist/cloud/commands/image.d.ts +2 -0
- package/dist/cloud/commands/image.js +98 -0
- package/dist/cloud/commands/index.d.ts +2 -0
- package/dist/cloud/commands/index.js +47 -0
- package/dist/cloud/commands/inventory.d.ts +2 -0
- package/dist/cloud/commands/inventory.js +13 -0
- package/dist/cloud/commands/iso.d.ts +2 -0
- package/dist/cloud/commands/iso.js +27 -0
- package/dist/cloud/commands/load-balancer-type.d.ts +2 -0
- package/dist/cloud/commands/load-balancer-type.js +24 -0
- package/dist/cloud/commands/load-balancer.d.ts +2 -0
- package/dist/cloud/commands/load-balancer.js +269 -0
- package/dist/cloud/commands/location.d.ts +2 -0
- package/dist/cloud/commands/location.js +24 -0
- package/dist/cloud/commands/network.d.ts +2 -0
- package/dist/cloud/commands/network.js +157 -0
- package/dist/cloud/commands/placement-group.d.ts +2 -0
- package/dist/cloud/commands/placement-group.js +79 -0
- package/dist/cloud/commands/pricing.d.ts +2 -0
- package/dist/cloud/commands/pricing.js +15 -0
- package/dist/cloud/commands/primary-ip.d.ts +2 -0
- package/dist/cloud/commands/primary-ip.js +139 -0
- package/dist/cloud/commands/server-type.d.ts +2 -0
- package/dist/cloud/commands/server-type.js +24 -0
- package/dist/cloud/commands/server.d.ts +2 -0
- package/dist/cloud/commands/server.js +346 -0
- package/dist/cloud/commands/ssh-key.d.ts +2 -0
- package/dist/cloud/commands/ssh-key.js +94 -0
- package/dist/cloud/commands/volume.d.ts +2 -0
- package/dist/cloud/commands/volume.js +138 -0
- package/dist/cloud/context.d.ts +34 -0
- package/dist/cloud/context.js +215 -0
- package/dist/cloud/formatter.d.ts +40 -0
- package/dist/cloud/formatter.js +824 -0
- package/dist/cloud/helpers.d.ts +27 -0
- package/dist/cloud/helpers.js +67 -0
- package/dist/cloud/inventory.d.ts +100 -0
- package/dist/cloud/inventory.js +229 -0
- package/dist/cloud/pricing.d.ts +27 -0
- package/dist/cloud/pricing.js +103 -0
- package/dist/cloud/token-instructions.d.ts +2 -0
- package/dist/cloud/token-instructions.js +21 -0
- package/dist/cloud/types.d.ts +437 -0
- package/dist/cloud/types.js +5 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -0
- package/dist/formatter.d.ts +3 -0
- package/dist/formatter.js +6 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +21 -0
- package/dist/robot/client.d.ts +256 -0
- package/dist/robot/client.js +745 -0
- package/dist/robot/commands/auth.d.ts +2 -0
- package/dist/robot/commands/auth.js +54 -0
- package/dist/robot/commands/boot.d.ts +2 -0
- package/dist/robot/commands/boot.js +76 -0
- package/dist/robot/commands/cancel.d.ts +2 -0
- package/dist/robot/commands/cancel.js +37 -0
- package/dist/robot/commands/failover.d.ts +2 -0
- package/dist/robot/commands/failover.js +46 -0
- package/dist/robot/commands/firewall.d.ts +2 -0
- package/dist/robot/commands/firewall.js +72 -0
- package/dist/robot/commands/index.d.ts +2 -0
- package/dist/robot/commands/index.js +36 -0
- package/dist/robot/commands/interactive.d.ts +2 -0
- package/dist/robot/commands/interactive.js +152 -0
- package/dist/robot/commands/ip.d.ts +2 -0
- package/dist/robot/commands/ip.js +55 -0
- package/dist/robot/commands/key.d.ts +2 -0
- package/dist/robot/commands/key.js +68 -0
- package/dist/robot/commands/order.d.ts +2 -0
- package/dist/robot/commands/order.js +33 -0
- package/dist/robot/commands/rdns.d.ts +2 -0
- package/dist/robot/commands/rdns.js +41 -0
- package/dist/robot/commands/reset.d.ts +2 -0
- package/dist/robot/commands/reset.js +77 -0
- package/dist/robot/commands/server.d.ts +2 -0
- package/dist/robot/commands/server.js +32 -0
- package/dist/robot/commands/storagebox.d.ts +2 -0
- package/dist/robot/commands/storagebox.js +126 -0
- package/dist/robot/commands/subnet.d.ts +2 -0
- package/dist/robot/commands/subnet.js +24 -0
- package/dist/robot/commands/traffic.d.ts +2 -0
- package/dist/robot/commands/traffic.js +23 -0
- package/dist/robot/commands/vswitch.d.ts +2 -0
- package/dist/robot/commands/vswitch.js +81 -0
- package/dist/robot/commands/wol.d.ts +2 -0
- package/dist/robot/commands/wol.js +20 -0
- package/dist/robot/formatter.d.ts +57 -0
- package/dist/robot/formatter.js +582 -0
- package/dist/robot/types.d.ts +359 -0
- package/dist/robot/types.js +5 -0
- package/dist/shared/config.d.ts +86 -0
- package/dist/shared/config.js +289 -0
- package/dist/shared/formatter.d.ts +29 -0
- package/dist/shared/formatter.js +121 -0
- package/dist/shared/helpers.d.ts +39 -0
- package/dist/shared/helpers.js +86 -0
- package/dist/shared/init.d.ts +2 -0
- package/dist/shared/init.js +107 -0
- package/dist/shared/reference.d.ts +5 -0
- package/dist/shared/reference.js +689 -0
- package/dist/shared/schemas.d.ts +1 -0
- package/dist/shared/schemas.js +1031 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +2 -0
- package/package.json +127 -4
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
import { colorize, createTable, formatBytes, formatDate, formatStatus, heading, info, warning, } from "../shared/formatter.js";
|
|
2
|
+
import { toNumber } from "./pricing.js";
|
|
3
|
+
// Context
|
|
4
|
+
export function formatContextList(contexts) {
|
|
5
|
+
if (contexts.length === 0) {
|
|
6
|
+
return info("No contexts configured. Run: hctl cloud context create");
|
|
7
|
+
}
|
|
8
|
+
const table = createTable(["Name", "Active"]);
|
|
9
|
+
for (const ctx of contexts) {
|
|
10
|
+
table.push([ctx.name, ctx.active ? colorize("*", "green") : ""]);
|
|
11
|
+
}
|
|
12
|
+
return table.toString();
|
|
13
|
+
}
|
|
14
|
+
// Datacenters
|
|
15
|
+
export function formatDatacenterList(datacenters) {
|
|
16
|
+
if (datacenters.length === 0) {
|
|
17
|
+
return info("No datacenters found.");
|
|
18
|
+
}
|
|
19
|
+
const table = createTable(["ID", "Name", "Description", "Location"]);
|
|
20
|
+
for (const dc of datacenters) {
|
|
21
|
+
table.push([dc.id.toString(), dc.name, dc.description, dc.location.name]);
|
|
22
|
+
}
|
|
23
|
+
return table.toString();
|
|
24
|
+
}
|
|
25
|
+
export function formatDatacenterDetails(dc) {
|
|
26
|
+
const lines = [];
|
|
27
|
+
lines.push(heading(`Datacenter: ${dc.name}`));
|
|
28
|
+
lines.push("");
|
|
29
|
+
const table = createTable(["Property", "Value"]);
|
|
30
|
+
table.push(["ID", dc.id.toString()], ["Name", dc.name], ["Description", dc.description], ["Location", dc.location.description], ["City", dc.location.city], ["Country", dc.location.country], ["Network Zone", dc.location.network_zone]);
|
|
31
|
+
lines.push(table.toString());
|
|
32
|
+
return lines.join("\n");
|
|
33
|
+
}
|
|
34
|
+
// Locations
|
|
35
|
+
export function formatLocationList(locations) {
|
|
36
|
+
if (locations.length === 0) {
|
|
37
|
+
return info("No locations found.");
|
|
38
|
+
}
|
|
39
|
+
const table = createTable([
|
|
40
|
+
"ID",
|
|
41
|
+
"Name",
|
|
42
|
+
"Description",
|
|
43
|
+
"City",
|
|
44
|
+
"Country",
|
|
45
|
+
"Network Zone",
|
|
46
|
+
]);
|
|
47
|
+
for (const loc of locations) {
|
|
48
|
+
table.push([
|
|
49
|
+
loc.id.toString(),
|
|
50
|
+
loc.name,
|
|
51
|
+
loc.description,
|
|
52
|
+
loc.city,
|
|
53
|
+
loc.country,
|
|
54
|
+
loc.network_zone,
|
|
55
|
+
]);
|
|
56
|
+
}
|
|
57
|
+
return table.toString();
|
|
58
|
+
}
|
|
59
|
+
export function formatLocationDetails(loc) {
|
|
60
|
+
const lines = [];
|
|
61
|
+
lines.push(heading(`Location: ${loc.name}`));
|
|
62
|
+
lines.push("");
|
|
63
|
+
const table = createTable(["Property", "Value"]);
|
|
64
|
+
table.push(["ID", loc.id.toString()], ["Name", loc.name], ["Description", loc.description], ["City", loc.city], ["Country", loc.country], ["Latitude", loc.latitude.toString()], ["Longitude", loc.longitude.toString()], ["Network Zone", loc.network_zone]);
|
|
65
|
+
lines.push(table.toString());
|
|
66
|
+
return lines.join("\n");
|
|
67
|
+
}
|
|
68
|
+
// Server Types
|
|
69
|
+
export function formatServerTypeList(types) {
|
|
70
|
+
if (types.length === 0) {
|
|
71
|
+
return info("No server types found.");
|
|
72
|
+
}
|
|
73
|
+
const table = createTable([
|
|
74
|
+
"ID",
|
|
75
|
+
"Name",
|
|
76
|
+
"Cores",
|
|
77
|
+
"Memory",
|
|
78
|
+
"Disk",
|
|
79
|
+
"Storage",
|
|
80
|
+
"CPU",
|
|
81
|
+
"Arch",
|
|
82
|
+
]);
|
|
83
|
+
for (const st of types) {
|
|
84
|
+
table.push([
|
|
85
|
+
st.id.toString(),
|
|
86
|
+
st.name,
|
|
87
|
+
st.cores.toString(),
|
|
88
|
+
`${st.memory} GB`,
|
|
89
|
+
`${st.disk} GB`,
|
|
90
|
+
st.storage_type,
|
|
91
|
+
st.cpu_type,
|
|
92
|
+
st.architecture,
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
95
|
+
return table.toString();
|
|
96
|
+
}
|
|
97
|
+
export function formatServerTypeDetails(st) {
|
|
98
|
+
const lines = [];
|
|
99
|
+
lines.push(heading(`Server Type: ${st.name}`));
|
|
100
|
+
lines.push("");
|
|
101
|
+
const table = createTable(["Property", "Value"]);
|
|
102
|
+
table.push(["ID", st.id.toString()], ["Name", st.name], ["Description", st.description], ["Cores", st.cores.toString()], ["Memory", `${st.memory} GB`], ["Disk", `${st.disk} GB`], ["Storage Type", st.storage_type], ["CPU Type", st.cpu_type], ["Architecture", st.architecture], ["Deprecated", st.deprecated ? colorize("Yes", "red") : "No"]);
|
|
103
|
+
lines.push(table.toString());
|
|
104
|
+
if (st.prices.length > 0) {
|
|
105
|
+
lines.push("");
|
|
106
|
+
lines.push(colorize("Pricing:", "bold"));
|
|
107
|
+
const priceTable = createTable([
|
|
108
|
+
"Location",
|
|
109
|
+
"Hourly",
|
|
110
|
+
"Monthly",
|
|
111
|
+
"Traffic",
|
|
112
|
+
]);
|
|
113
|
+
for (const p of st.prices) {
|
|
114
|
+
priceTable.push([
|
|
115
|
+
p.location,
|
|
116
|
+
`€${p.price_hourly.gross}`,
|
|
117
|
+
`€${p.price_monthly.gross}`,
|
|
118
|
+
formatBytes(p.included_traffic),
|
|
119
|
+
]);
|
|
120
|
+
}
|
|
121
|
+
lines.push(priceTable.toString());
|
|
122
|
+
}
|
|
123
|
+
return lines.join("\n");
|
|
124
|
+
}
|
|
125
|
+
// Load Balancer Types
|
|
126
|
+
export function formatLoadBalancerTypeList(types) {
|
|
127
|
+
if (types.length === 0) {
|
|
128
|
+
return info("No load balancer types found.");
|
|
129
|
+
}
|
|
130
|
+
const table = createTable([
|
|
131
|
+
"ID",
|
|
132
|
+
"Name",
|
|
133
|
+
"Description",
|
|
134
|
+
"Max Conn",
|
|
135
|
+
"Max Services",
|
|
136
|
+
"Max Targets",
|
|
137
|
+
]);
|
|
138
|
+
for (const lbt of types) {
|
|
139
|
+
table.push([
|
|
140
|
+
lbt.id.toString(),
|
|
141
|
+
lbt.name,
|
|
142
|
+
lbt.description,
|
|
143
|
+
lbt.max_connections.toString(),
|
|
144
|
+
lbt.max_services.toString(),
|
|
145
|
+
lbt.max_targets.toString(),
|
|
146
|
+
]);
|
|
147
|
+
}
|
|
148
|
+
return table.toString();
|
|
149
|
+
}
|
|
150
|
+
export function formatLoadBalancerTypeDetails(lbt) {
|
|
151
|
+
const lines = [];
|
|
152
|
+
lines.push(heading(`Load Balancer Type: ${lbt.name}`));
|
|
153
|
+
lines.push("");
|
|
154
|
+
const table = createTable(["Property", "Value"]);
|
|
155
|
+
table.push(["ID", lbt.id.toString()], ["Name", lbt.name], ["Description", lbt.description], ["Max Connections", lbt.max_connections.toString()], ["Max Services", lbt.max_services.toString()], ["Max Targets", lbt.max_targets.toString()], ["Max Certificates", lbt.max_assigned_certificates.toString()], ["Deprecated", lbt.deprecated ? colorize("Yes", "red") : "No"]);
|
|
156
|
+
lines.push(table.toString());
|
|
157
|
+
return lines.join("\n");
|
|
158
|
+
}
|
|
159
|
+
// ISOs
|
|
160
|
+
export function formatIsoList(isos) {
|
|
161
|
+
if (isos.length === 0) {
|
|
162
|
+
return info("No ISOs found.");
|
|
163
|
+
}
|
|
164
|
+
const table = createTable(["ID", "Name", "Type", "Architecture"]);
|
|
165
|
+
for (const iso of isos) {
|
|
166
|
+
table.push([
|
|
167
|
+
iso.id.toString(),
|
|
168
|
+
iso.name,
|
|
169
|
+
iso.type,
|
|
170
|
+
iso.architecture || "-",
|
|
171
|
+
]);
|
|
172
|
+
}
|
|
173
|
+
return table.toString();
|
|
174
|
+
}
|
|
175
|
+
export function formatIsoDetails(iso) {
|
|
176
|
+
const lines = [];
|
|
177
|
+
lines.push(heading(`ISO: ${iso.name}`));
|
|
178
|
+
lines.push("");
|
|
179
|
+
const table = createTable(["Property", "Value"]);
|
|
180
|
+
table.push(["ID", iso.id.toString()], ["Name", iso.name], ["Description", iso.description], ["Type", iso.type], ["Architecture", iso.architecture || "-"]);
|
|
181
|
+
lines.push(table.toString());
|
|
182
|
+
return lines.join("\n");
|
|
183
|
+
}
|
|
184
|
+
// Cloud Servers
|
|
185
|
+
export function formatCloudServerList(servers) {
|
|
186
|
+
if (servers.length === 0) {
|
|
187
|
+
return info("No servers found.");
|
|
188
|
+
}
|
|
189
|
+
const table = createTable([
|
|
190
|
+
"ID",
|
|
191
|
+
"Name",
|
|
192
|
+
"Status",
|
|
193
|
+
"Type",
|
|
194
|
+
"DC",
|
|
195
|
+
"IPv4",
|
|
196
|
+
"Labels",
|
|
197
|
+
]);
|
|
198
|
+
for (const srv of servers) {
|
|
199
|
+
const labels = Object.entries(srv.labels)
|
|
200
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
201
|
+
.join(", ") || "-";
|
|
202
|
+
table.push([
|
|
203
|
+
srv.id.toString(),
|
|
204
|
+
srv.name,
|
|
205
|
+
formatStatus(srv.status),
|
|
206
|
+
srv.server_type?.name ?? "-",
|
|
207
|
+
srv.location?.name ?? "-",
|
|
208
|
+
srv.public_net?.ipv4?.ip ?? "-",
|
|
209
|
+
labels.length > 30 ? `${labels.substring(0, 27)}...` : labels,
|
|
210
|
+
]);
|
|
211
|
+
}
|
|
212
|
+
return table.toString();
|
|
213
|
+
}
|
|
214
|
+
export function formatCloudServerDetails(srv) {
|
|
215
|
+
const lines = [];
|
|
216
|
+
lines.push(heading(`Server: ${srv.name}`));
|
|
217
|
+
lines.push("");
|
|
218
|
+
const table = createTable(["Property", "Value"]);
|
|
219
|
+
table.push(["ID", srv.id.toString()], ["Name", srv.name], ["Status", formatStatus(srv.status)], [
|
|
220
|
+
"Server Type",
|
|
221
|
+
srv.server_type
|
|
222
|
+
? `${srv.server_type.name} (${srv.server_type.cores} cores, ${srv.server_type.memory} GB RAM)`
|
|
223
|
+
: "-",
|
|
224
|
+
], [
|
|
225
|
+
"Datacenter",
|
|
226
|
+
srv.location ? `${srv.location.name} (${srv.location.city})` : "-",
|
|
227
|
+
], [
|
|
228
|
+
"Image",
|
|
229
|
+
srv.image
|
|
230
|
+
? `${srv.image.description} (${srv.image.name || srv.image.id})`
|
|
231
|
+
: "-",
|
|
232
|
+
], ["IPv4", srv.public_net?.ipv4?.ip || "-"], ["IPv6", srv.public_net?.ipv6?.ip || "-"], ["Primary Disk", `${srv.primary_disk_size} GB`], ["Rescue", srv.rescue_enabled ? colorize("Enabled", "yellow") : "Disabled"], ["Locked", srv.locked ? colorize("Yes", "red") : "No"], ["Backup Window", srv.backup_window || "Disabled"], [
|
|
233
|
+
"Protection",
|
|
234
|
+
srv.protection.delete ? colorize("Delete protected", "yellow") : "None",
|
|
235
|
+
], ["Created", formatDate(srv.created)]);
|
|
236
|
+
lines.push(table.toString());
|
|
237
|
+
if (Object.keys(srv.labels).length > 0) {
|
|
238
|
+
lines.push("");
|
|
239
|
+
lines.push(colorize("Labels:", "bold"));
|
|
240
|
+
for (const [k, v] of Object.entries(srv.labels)) {
|
|
241
|
+
lines.push(` ${k} = ${v}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (srv.private_net.length > 0) {
|
|
245
|
+
lines.push("");
|
|
246
|
+
lines.push(colorize("Private Networks:", "bold"));
|
|
247
|
+
const netTable = createTable(["Network", "IP", "Alias IPs"]);
|
|
248
|
+
for (const pn of srv.private_net) {
|
|
249
|
+
netTable.push([
|
|
250
|
+
pn.network.toString(),
|
|
251
|
+
pn.ip,
|
|
252
|
+
pn.alias_ips.join(", ") || "-",
|
|
253
|
+
]);
|
|
254
|
+
}
|
|
255
|
+
lines.push(netTable.toString());
|
|
256
|
+
}
|
|
257
|
+
if (srv.volumes.length > 0) {
|
|
258
|
+
lines.push("");
|
|
259
|
+
lines.push(colorize("Volumes:", "bold"));
|
|
260
|
+
lines.push(` ${srv.volumes.join(", ")}`);
|
|
261
|
+
}
|
|
262
|
+
return lines.join("\n");
|
|
263
|
+
}
|
|
264
|
+
// Networks
|
|
265
|
+
export function formatNetworkList(networks) {
|
|
266
|
+
if (networks.length === 0) {
|
|
267
|
+
return info("No networks found.");
|
|
268
|
+
}
|
|
269
|
+
const table = createTable(["ID", "Name", "IP Range", "Subnets", "Servers"]);
|
|
270
|
+
for (const net of networks) {
|
|
271
|
+
table.push([
|
|
272
|
+
net.id.toString(),
|
|
273
|
+
net.name,
|
|
274
|
+
net.ip_range,
|
|
275
|
+
net.subnets.length.toString(),
|
|
276
|
+
net.servers.length.toString(),
|
|
277
|
+
]);
|
|
278
|
+
}
|
|
279
|
+
return table.toString();
|
|
280
|
+
}
|
|
281
|
+
export function formatNetworkDetails(net) {
|
|
282
|
+
const lines = [];
|
|
283
|
+
lines.push(heading(`Network: ${net.name}`));
|
|
284
|
+
lines.push("");
|
|
285
|
+
const table = createTable(["Property", "Value"]);
|
|
286
|
+
table.push(["ID", net.id.toString()], ["Name", net.name], ["IP Range", net.ip_range], ["Servers", net.servers.length.toString()], [
|
|
287
|
+
"Protection",
|
|
288
|
+
net.protection.delete ? colorize("Delete protected", "yellow") : "None",
|
|
289
|
+
], ["Created", formatDate(net.created)]);
|
|
290
|
+
lines.push(table.toString());
|
|
291
|
+
if (net.subnets.length > 0) {
|
|
292
|
+
lines.push("");
|
|
293
|
+
lines.push(colorize("Subnets:", "bold"));
|
|
294
|
+
const subTable = createTable([
|
|
295
|
+
"IP Range",
|
|
296
|
+
"Type",
|
|
297
|
+
"Network Zone",
|
|
298
|
+
"Gateway",
|
|
299
|
+
]);
|
|
300
|
+
for (const s of net.subnets) {
|
|
301
|
+
subTable.push([s.ip_range, s.type, s.network_zone, s.gateway]);
|
|
302
|
+
}
|
|
303
|
+
lines.push(subTable.toString());
|
|
304
|
+
}
|
|
305
|
+
if (net.routes.length > 0) {
|
|
306
|
+
lines.push("");
|
|
307
|
+
lines.push(colorize("Routes:", "bold"));
|
|
308
|
+
const routeTable = createTable(["Destination", "Gateway"]);
|
|
309
|
+
for (const r of net.routes) {
|
|
310
|
+
routeTable.push([r.destination, r.gateway]);
|
|
311
|
+
}
|
|
312
|
+
lines.push(routeTable.toString());
|
|
313
|
+
}
|
|
314
|
+
return lines.join("\n");
|
|
315
|
+
}
|
|
316
|
+
// Cloud Firewalls
|
|
317
|
+
export function formatCloudFirewallList(firewalls) {
|
|
318
|
+
if (firewalls.length === 0) {
|
|
319
|
+
return info("No firewalls found.");
|
|
320
|
+
}
|
|
321
|
+
const table = createTable(["ID", "Name", "Rules", "Applied To", "Created"]);
|
|
322
|
+
for (const fw of firewalls) {
|
|
323
|
+
table.push([
|
|
324
|
+
fw.id.toString(),
|
|
325
|
+
fw.name,
|
|
326
|
+
fw.rules.length.toString(),
|
|
327
|
+
fw.applied_to.length.toString(),
|
|
328
|
+
formatDate(fw.created),
|
|
329
|
+
]);
|
|
330
|
+
}
|
|
331
|
+
return table.toString();
|
|
332
|
+
}
|
|
333
|
+
export function formatCloudFirewallDetails(fw) {
|
|
334
|
+
const lines = [];
|
|
335
|
+
lines.push(heading(`Firewall: ${fw.name}`));
|
|
336
|
+
lines.push("");
|
|
337
|
+
const table = createTable(["Property", "Value"]);
|
|
338
|
+
table.push(["ID", fw.id.toString()], ["Name", fw.name], ["Created", formatDate(fw.created)]);
|
|
339
|
+
lines.push(table.toString());
|
|
340
|
+
if (fw.rules.length > 0) {
|
|
341
|
+
lines.push("");
|
|
342
|
+
lines.push(colorize("Rules:", "bold"));
|
|
343
|
+
const ruleTable = createTable([
|
|
344
|
+
"Direction",
|
|
345
|
+
"Protocol",
|
|
346
|
+
"Port",
|
|
347
|
+
"Source IPs",
|
|
348
|
+
"Description",
|
|
349
|
+
]);
|
|
350
|
+
for (const r of fw.rules) {
|
|
351
|
+
ruleTable.push([
|
|
352
|
+
r.direction,
|
|
353
|
+
r.protocol,
|
|
354
|
+
r.port || "any",
|
|
355
|
+
r.source_ips.join(", ").substring(0, 30) || "-",
|
|
356
|
+
r.description || "-",
|
|
357
|
+
]);
|
|
358
|
+
}
|
|
359
|
+
lines.push(ruleTable.toString());
|
|
360
|
+
}
|
|
361
|
+
return lines.join("\n");
|
|
362
|
+
}
|
|
363
|
+
// Floating IPs
|
|
364
|
+
export function formatFloatingIpList(ips) {
|
|
365
|
+
if (ips.length === 0) {
|
|
366
|
+
return info("No floating IPs found.");
|
|
367
|
+
}
|
|
368
|
+
const table = createTable([
|
|
369
|
+
"ID",
|
|
370
|
+
"Name",
|
|
371
|
+
"IP",
|
|
372
|
+
"Type",
|
|
373
|
+
"Server",
|
|
374
|
+
"Location",
|
|
375
|
+
"Blocked",
|
|
376
|
+
]);
|
|
377
|
+
for (const ip of ips) {
|
|
378
|
+
table.push([
|
|
379
|
+
ip.id.toString(),
|
|
380
|
+
ip.name,
|
|
381
|
+
ip.ip,
|
|
382
|
+
ip.type,
|
|
383
|
+
ip.server?.toString() || "-",
|
|
384
|
+
ip.home_location.name,
|
|
385
|
+
ip.blocked ? colorize("Yes", "red") : "No",
|
|
386
|
+
]);
|
|
387
|
+
}
|
|
388
|
+
return table.toString();
|
|
389
|
+
}
|
|
390
|
+
export function formatFloatingIpDetails(ip) {
|
|
391
|
+
const lines = [];
|
|
392
|
+
lines.push(heading(`Floating IP: ${ip.name}`));
|
|
393
|
+
lines.push("");
|
|
394
|
+
const table = createTable(["Property", "Value"]);
|
|
395
|
+
table.push(["ID", ip.id.toString()], ["Name", ip.name], ["Description", ip.description || "-"], ["IP", ip.ip], ["Type", ip.type], ["Server", ip.server?.toString() || "Unassigned"], ["Home Location", ip.home_location.name], ["Blocked", ip.blocked ? colorize("Yes", "red") : "No"], [
|
|
396
|
+
"Protection",
|
|
397
|
+
ip.protection.delete ? colorize("Delete protected", "yellow") : "None",
|
|
398
|
+
], ["Created", formatDate(ip.created)]);
|
|
399
|
+
lines.push(table.toString());
|
|
400
|
+
return lines.join("\n");
|
|
401
|
+
}
|
|
402
|
+
// Primary IPs
|
|
403
|
+
export function formatPrimaryIpList(ips) {
|
|
404
|
+
if (ips.length === 0) {
|
|
405
|
+
return info("No primary IPs found.");
|
|
406
|
+
}
|
|
407
|
+
const table = createTable([
|
|
408
|
+
"ID",
|
|
409
|
+
"Name",
|
|
410
|
+
"IP",
|
|
411
|
+
"Type",
|
|
412
|
+
"Assignee",
|
|
413
|
+
"Location",
|
|
414
|
+
"Auto Delete",
|
|
415
|
+
]);
|
|
416
|
+
for (const ip of ips) {
|
|
417
|
+
table.push([
|
|
418
|
+
ip.id.toString(),
|
|
419
|
+
ip.name,
|
|
420
|
+
ip.ip,
|
|
421
|
+
ip.type,
|
|
422
|
+
ip.assignee_id?.toString() || "-",
|
|
423
|
+
ip.location.name,
|
|
424
|
+
ip.auto_delete ? "Yes" : "No",
|
|
425
|
+
]);
|
|
426
|
+
}
|
|
427
|
+
return table.toString();
|
|
428
|
+
}
|
|
429
|
+
export function formatPrimaryIpDetails(ip) {
|
|
430
|
+
const lines = [];
|
|
431
|
+
lines.push(heading(`Primary IP: ${ip.name}`));
|
|
432
|
+
lines.push("");
|
|
433
|
+
const table = createTable(["Property", "Value"]);
|
|
434
|
+
table.push(["ID", ip.id.toString()], ["Name", ip.name], ["IP", ip.ip], ["Type", ip.type], ["Assignee", ip.assignee_id?.toString() || "Unassigned"], ["Location", ip.location.name], ["Auto Delete", ip.auto_delete ? "Yes" : "No"], ["Blocked", ip.blocked ? colorize("Yes", "red") : "No"], [
|
|
435
|
+
"Protection",
|
|
436
|
+
ip.protection.delete ? colorize("Delete protected", "yellow") : "None",
|
|
437
|
+
], ["Created", formatDate(ip.created)]);
|
|
438
|
+
lines.push(table.toString());
|
|
439
|
+
return lines.join("\n");
|
|
440
|
+
}
|
|
441
|
+
// Volumes
|
|
442
|
+
export function formatVolumeList(volumes) {
|
|
443
|
+
if (volumes.length === 0) {
|
|
444
|
+
return info("No volumes found.");
|
|
445
|
+
}
|
|
446
|
+
const table = createTable([
|
|
447
|
+
"ID",
|
|
448
|
+
"Name",
|
|
449
|
+
"Size",
|
|
450
|
+
"Server",
|
|
451
|
+
"Status",
|
|
452
|
+
"Location",
|
|
453
|
+
"Format",
|
|
454
|
+
]);
|
|
455
|
+
for (const vol of volumes) {
|
|
456
|
+
table.push([
|
|
457
|
+
vol.id.toString(),
|
|
458
|
+
vol.name,
|
|
459
|
+
`${vol.size} GB`,
|
|
460
|
+
vol.server?.toString() || "-",
|
|
461
|
+
formatStatus(vol.status),
|
|
462
|
+
vol.location.name,
|
|
463
|
+
vol.format || "-",
|
|
464
|
+
]);
|
|
465
|
+
}
|
|
466
|
+
return table.toString();
|
|
467
|
+
}
|
|
468
|
+
export function formatVolumeDetails(vol) {
|
|
469
|
+
const lines = [];
|
|
470
|
+
lines.push(heading(`Volume: ${vol.name}`));
|
|
471
|
+
lines.push("");
|
|
472
|
+
const table = createTable(["Property", "Value"]);
|
|
473
|
+
table.push(["ID", vol.id.toString()], ["Name", vol.name], ["Size", `${vol.size} GB`], ["Server", vol.server?.toString() || "Unattached"], ["Status", formatStatus(vol.status)], ["Location", vol.location.name], ["Linux Device", vol.linux_device || "-"], ["Format", vol.format || "-"], [
|
|
474
|
+
"Protection",
|
|
475
|
+
vol.protection.delete ? colorize("Delete protected", "yellow") : "None",
|
|
476
|
+
], ["Created", formatDate(vol.created)]);
|
|
477
|
+
lines.push(table.toString());
|
|
478
|
+
return lines.join("\n");
|
|
479
|
+
}
|
|
480
|
+
// Load Balancers
|
|
481
|
+
export function formatLoadBalancerList(lbs) {
|
|
482
|
+
if (lbs.length === 0) {
|
|
483
|
+
return info("No load balancers found.");
|
|
484
|
+
}
|
|
485
|
+
const table = createTable([
|
|
486
|
+
"ID",
|
|
487
|
+
"Name",
|
|
488
|
+
"IPv4",
|
|
489
|
+
"Type",
|
|
490
|
+
"Location",
|
|
491
|
+
"Targets",
|
|
492
|
+
"Services",
|
|
493
|
+
]);
|
|
494
|
+
for (const lb of lbs) {
|
|
495
|
+
table.push([
|
|
496
|
+
lb.id.toString(),
|
|
497
|
+
lb.name,
|
|
498
|
+
lb.public_net.ipv4.ip,
|
|
499
|
+
lb.load_balancer_type.name,
|
|
500
|
+
lb.location.name,
|
|
501
|
+
lb.targets.length.toString(),
|
|
502
|
+
lb.services.length.toString(),
|
|
503
|
+
]);
|
|
504
|
+
}
|
|
505
|
+
return table.toString();
|
|
506
|
+
}
|
|
507
|
+
export function formatLoadBalancerDetails(lb) {
|
|
508
|
+
const lines = [];
|
|
509
|
+
lines.push(heading(`Load Balancer: ${lb.name}`));
|
|
510
|
+
lines.push("");
|
|
511
|
+
const table = createTable(["Property", "Value"]);
|
|
512
|
+
table.push(["ID", lb.id.toString()], ["Name", lb.name], ["Type", lb.load_balancer_type.name], ["Location", lb.location.name], ["IPv4", lb.public_net.ipv4.ip], ["IPv6", lb.public_net.ipv6.ip], ["Public Net", lb.public_net.enabled ? "Enabled" : "Disabled"], ["Algorithm", lb.algorithm.type], [
|
|
513
|
+
"Protection",
|
|
514
|
+
lb.protection.delete ? colorize("Delete protected", "yellow") : "None",
|
|
515
|
+
], ["Created", formatDate(lb.created)]);
|
|
516
|
+
lines.push(table.toString());
|
|
517
|
+
if (lb.services.length > 0) {
|
|
518
|
+
lines.push("");
|
|
519
|
+
lines.push(colorize("Services:", "bold"));
|
|
520
|
+
const svcTable = createTable([
|
|
521
|
+
"Protocol",
|
|
522
|
+
"Listen Port",
|
|
523
|
+
"Destination Port",
|
|
524
|
+
"Proxyprotocol",
|
|
525
|
+
]);
|
|
526
|
+
for (const svc of lb.services) {
|
|
527
|
+
svcTable.push([
|
|
528
|
+
svc.protocol,
|
|
529
|
+
svc.listen_port.toString(),
|
|
530
|
+
svc.destination_port.toString(),
|
|
531
|
+
svc.proxyprotocol ? "Yes" : "No",
|
|
532
|
+
]);
|
|
533
|
+
}
|
|
534
|
+
lines.push(svcTable.toString());
|
|
535
|
+
}
|
|
536
|
+
return lines.join("\n");
|
|
537
|
+
}
|
|
538
|
+
// Images
|
|
539
|
+
export function formatImageList(images) {
|
|
540
|
+
if (images.length === 0) {
|
|
541
|
+
return info("No images found.");
|
|
542
|
+
}
|
|
543
|
+
const table = createTable([
|
|
544
|
+
"ID",
|
|
545
|
+
"Type",
|
|
546
|
+
"Name",
|
|
547
|
+
"Description",
|
|
548
|
+
"OS",
|
|
549
|
+
"Arch",
|
|
550
|
+
"Disk",
|
|
551
|
+
"Status",
|
|
552
|
+
]);
|
|
553
|
+
for (const img of images) {
|
|
554
|
+
table.push([
|
|
555
|
+
img.id.toString(),
|
|
556
|
+
img.type,
|
|
557
|
+
img.name || "-",
|
|
558
|
+
img.description.substring(0, 30),
|
|
559
|
+
img.os_flavor,
|
|
560
|
+
img.architecture,
|
|
561
|
+
`${img.disk_size} GB`,
|
|
562
|
+
formatStatus(img.status),
|
|
563
|
+
]);
|
|
564
|
+
}
|
|
565
|
+
return table.toString();
|
|
566
|
+
}
|
|
567
|
+
export function formatImageDetails(img) {
|
|
568
|
+
const lines = [];
|
|
569
|
+
lines.push(heading(`Image: ${img.description}`));
|
|
570
|
+
lines.push("");
|
|
571
|
+
const table = createTable(["Property", "Value"]);
|
|
572
|
+
table.push(["ID", img.id.toString()], ["Type", img.type], ["Name", img.name || "-"], ["Description", img.description], ["OS Flavor", img.os_flavor], ["OS Version", img.os_version || "-"], ["Architecture", img.architecture], ["Disk Size", `${img.disk_size} GB`], [
|
|
573
|
+
"Image Size",
|
|
574
|
+
img.image_size ? formatBytes(img.image_size * 1024 * 1024) : "-",
|
|
575
|
+
], ["Status", formatStatus(img.status)], ["Rapid Deploy", img.rapid_deploy ? "Yes" : "No"], [
|
|
576
|
+
"Protection",
|
|
577
|
+
img.protection.delete ? colorize("Delete protected", "yellow") : "None",
|
|
578
|
+
], ["Created", formatDate(img.created)]);
|
|
579
|
+
lines.push(table.toString());
|
|
580
|
+
return lines.join("\n");
|
|
581
|
+
}
|
|
582
|
+
// Cloud SSH Keys
|
|
583
|
+
export function formatCloudSshKeyList(keys) {
|
|
584
|
+
if (keys.length === 0) {
|
|
585
|
+
return info("No SSH keys found.");
|
|
586
|
+
}
|
|
587
|
+
const table = createTable(["ID", "Name", "Fingerprint", "Created"]);
|
|
588
|
+
for (const key of keys) {
|
|
589
|
+
table.push([
|
|
590
|
+
key.id.toString(),
|
|
591
|
+
key.name,
|
|
592
|
+
key.fingerprint,
|
|
593
|
+
formatDate(key.created),
|
|
594
|
+
]);
|
|
595
|
+
}
|
|
596
|
+
return table.toString();
|
|
597
|
+
}
|
|
598
|
+
export function formatCloudSshKeyDetails(key) {
|
|
599
|
+
const lines = [];
|
|
600
|
+
lines.push(heading(`SSH Key: ${key.name}`));
|
|
601
|
+
lines.push("");
|
|
602
|
+
const table = createTable(["Property", "Value"]);
|
|
603
|
+
table.push(["ID", key.id.toString()], ["Name", key.name], ["Fingerprint", key.fingerprint], ["Created", formatDate(key.created)]);
|
|
604
|
+
lines.push(table.toString());
|
|
605
|
+
lines.push("");
|
|
606
|
+
lines.push(colorize("Public Key:", "bold"));
|
|
607
|
+
lines.push(key.public_key);
|
|
608
|
+
return lines.join("\n");
|
|
609
|
+
}
|
|
610
|
+
// Certificates
|
|
611
|
+
export function formatCertificateList(certs) {
|
|
612
|
+
if (certs.length === 0) {
|
|
613
|
+
return info("No certificates found.");
|
|
614
|
+
}
|
|
615
|
+
const table = createTable([
|
|
616
|
+
"ID",
|
|
617
|
+
"Name",
|
|
618
|
+
"Type",
|
|
619
|
+
"Domains",
|
|
620
|
+
"Valid Until",
|
|
621
|
+
"Created",
|
|
622
|
+
]);
|
|
623
|
+
for (const cert of certs) {
|
|
624
|
+
table.push([
|
|
625
|
+
cert.id.toString(),
|
|
626
|
+
cert.name,
|
|
627
|
+
cert.type,
|
|
628
|
+
cert.domain_names.join(", ").substring(0, 30),
|
|
629
|
+
formatDate(cert.not_valid_after),
|
|
630
|
+
formatDate(cert.created),
|
|
631
|
+
]);
|
|
632
|
+
}
|
|
633
|
+
return table.toString();
|
|
634
|
+
}
|
|
635
|
+
export function formatCertificateDetails(cert) {
|
|
636
|
+
const lines = [];
|
|
637
|
+
lines.push(heading(`Certificate: ${cert.name}`));
|
|
638
|
+
lines.push("");
|
|
639
|
+
const table = createTable(["Property", "Value"]);
|
|
640
|
+
table.push(["ID", cert.id.toString()], ["Name", cert.name], ["Type", cert.type], ["Domains", cert.domain_names.join(", ")], ["Fingerprint", cert.fingerprint || "-"], ["Valid From", formatDate(cert.not_valid_before)], ["Valid Until", formatDate(cert.not_valid_after)], ["Created", formatDate(cert.created)]);
|
|
641
|
+
lines.push(table.toString());
|
|
642
|
+
return lines.join("\n");
|
|
643
|
+
}
|
|
644
|
+
// Placement Groups
|
|
645
|
+
export function formatPlacementGroupList(groups) {
|
|
646
|
+
if (groups.length === 0) {
|
|
647
|
+
return info("No placement groups found.");
|
|
648
|
+
}
|
|
649
|
+
const table = createTable(["ID", "Name", "Type", "Servers", "Created"]);
|
|
650
|
+
for (const pg of groups) {
|
|
651
|
+
table.push([
|
|
652
|
+
pg.id.toString(),
|
|
653
|
+
pg.name,
|
|
654
|
+
pg.type,
|
|
655
|
+
pg.servers.length.toString(),
|
|
656
|
+
formatDate(pg.created),
|
|
657
|
+
]);
|
|
658
|
+
}
|
|
659
|
+
return table.toString();
|
|
660
|
+
}
|
|
661
|
+
export function formatPlacementGroupDetails(pg) {
|
|
662
|
+
const lines = [];
|
|
663
|
+
lines.push(heading(`Placement Group: ${pg.name}`));
|
|
664
|
+
lines.push("");
|
|
665
|
+
const table = createTable(["Property", "Value"]);
|
|
666
|
+
table.push(["ID", pg.id.toString()], ["Name", pg.name], ["Type", pg.type], ["Servers", pg.servers.length > 0 ? pg.servers.join(", ") : "-"], ["Created", formatDate(pg.created)]);
|
|
667
|
+
lines.push(table.toString());
|
|
668
|
+
return lines.join("\n");
|
|
669
|
+
}
|
|
670
|
+
// Pricing
|
|
671
|
+
export function formatPricingSummary(pricing) {
|
|
672
|
+
const lines = [
|
|
673
|
+
heading("Pricing"),
|
|
674
|
+
info(`Currency ${pricing.currency}, VAT ${pricing.vat_rate}% — list prices from GET /pricing (not per-account billing)`),
|
|
675
|
+
"",
|
|
676
|
+
];
|
|
677
|
+
const serverTable = createTable([
|
|
678
|
+
"Server Type",
|
|
679
|
+
"Location",
|
|
680
|
+
"Monthly (gross)",
|
|
681
|
+
"Hourly (gross)",
|
|
682
|
+
]);
|
|
683
|
+
for (const st of pricing.server_types) {
|
|
684
|
+
for (const p of st.prices) {
|
|
685
|
+
serverTable.push([
|
|
686
|
+
st.name,
|
|
687
|
+
p.location,
|
|
688
|
+
toNumber(p.price_monthly)?.toFixed(2) ?? "?",
|
|
689
|
+
toNumber(p.price_hourly)?.toFixed(4) ?? "?",
|
|
690
|
+
]);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
lines.push(heading("Server Types"), serverTable.toString(), "");
|
|
694
|
+
const lbTable = createTable(["LB Type", "Location", "Monthly (gross)"]);
|
|
695
|
+
for (const lb of pricing.load_balancer_types) {
|
|
696
|
+
for (const p of lb.prices) {
|
|
697
|
+
lbTable.push([
|
|
698
|
+
lb.name,
|
|
699
|
+
p.location,
|
|
700
|
+
toNumber(p.price_monthly)?.toFixed(2) ?? "?",
|
|
701
|
+
]);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
lines.push(heading("Load Balancer Types"), lbTable.toString(), "");
|
|
705
|
+
const ipTable = createTable(["Kind", "Type", "Location", "Monthly (gross)"]);
|
|
706
|
+
for (const ip of pricing.primary_ips) {
|
|
707
|
+
for (const p of ip.prices) {
|
|
708
|
+
ipTable.push([
|
|
709
|
+
"primary",
|
|
710
|
+
ip.type,
|
|
711
|
+
p.location,
|
|
712
|
+
toNumber(p.price_monthly)?.toFixed(2) ?? "?",
|
|
713
|
+
]);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
for (const ip of pricing.floating_ips) {
|
|
717
|
+
for (const p of ip.prices) {
|
|
718
|
+
ipTable.push([
|
|
719
|
+
"floating",
|
|
720
|
+
ip.type,
|
|
721
|
+
p.location,
|
|
722
|
+
toNumber(p.price_monthly)?.toFixed(2) ?? "?",
|
|
723
|
+
]);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
lines.push(heading("IPs"), ipTable.toString(), "");
|
|
727
|
+
lines.push(heading("Storage"), `Volume: ${toNumber(pricing.volume.price_per_gb_month)?.toFixed(4)} ${pricing.currency}/GB-month (gross)`, `Image: ${toNumber(pricing.image.price_per_gb_month)?.toFixed(4)} ${pricing.currency}/GB-month (gross)`, `Server backup: ${pricing.server_backup.percentage}% of server price`, "", info("Hetzner has no billing/invoice API; actual monthly cost depends on runtime, traffic overage and contract discounts."));
|
|
728
|
+
return lines.join("\n");
|
|
729
|
+
}
|
|
730
|
+
// Inventory
|
|
731
|
+
function formatCost(value) {
|
|
732
|
+
return value === null ? "?" : value.toFixed(2);
|
|
733
|
+
}
|
|
734
|
+
function appendTable(lines, title, headers, rows) {
|
|
735
|
+
if (rows.length === 0) {
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
const table = createTable(headers);
|
|
739
|
+
table.push(...rows);
|
|
740
|
+
lines.push(heading(`${title} (${rows.length})`), table.toString(), "");
|
|
741
|
+
}
|
|
742
|
+
function inventoryServerRow(s) {
|
|
743
|
+
return [
|
|
744
|
+
String(s.id),
|
|
745
|
+
s.name,
|
|
746
|
+
s.type ?? "?",
|
|
747
|
+
s.location,
|
|
748
|
+
formatStatus(s.status),
|
|
749
|
+
s.ipv4 ?? "-",
|
|
750
|
+
`${s.primaryDiskGb}G`,
|
|
751
|
+
String(s.volumeGb),
|
|
752
|
+
s.backup ? "yes" : "no",
|
|
753
|
+
formatCost(s.monthly),
|
|
754
|
+
];
|
|
755
|
+
}
|
|
756
|
+
function inventoryVolumeRow(v) {
|
|
757
|
+
return [
|
|
758
|
+
String(v.id),
|
|
759
|
+
v.name,
|
|
760
|
+
`${v.sizeGb}G`,
|
|
761
|
+
v.location,
|
|
762
|
+
v.status,
|
|
763
|
+
v.attachedTo === null ? "-" : String(v.attachedTo),
|
|
764
|
+
formatCost(v.monthly),
|
|
765
|
+
];
|
|
766
|
+
}
|
|
767
|
+
function inventoryIpRow(ip) {
|
|
768
|
+
return [
|
|
769
|
+
String(ip.id),
|
|
770
|
+
ip.kind,
|
|
771
|
+
ip.ip,
|
|
772
|
+
ip.location,
|
|
773
|
+
ip.assignedTo ?? "-",
|
|
774
|
+
formatCost(ip.monthly),
|
|
775
|
+
];
|
|
776
|
+
}
|
|
777
|
+
function inventoryLoadBalancerRow(lb) {
|
|
778
|
+
return [
|
|
779
|
+
String(lb.id),
|
|
780
|
+
lb.name,
|
|
781
|
+
lb.type ?? "?",
|
|
782
|
+
lb.location,
|
|
783
|
+
String(lb.targets),
|
|
784
|
+
formatCost(lb.monthly),
|
|
785
|
+
];
|
|
786
|
+
}
|
|
787
|
+
function inventoryImageRow(i) {
|
|
788
|
+
return [
|
|
789
|
+
String(i.id),
|
|
790
|
+
i.name ?? "-",
|
|
791
|
+
i.type,
|
|
792
|
+
`${i.sizeGb}G`,
|
|
793
|
+
formatCost(i.monthly),
|
|
794
|
+
];
|
|
795
|
+
}
|
|
796
|
+
export function formatInventoryReport(report) {
|
|
797
|
+
const lines = [heading("Fleet Inventory"), ""];
|
|
798
|
+
appendTable(lines, "Servers", [
|
|
799
|
+
"ID",
|
|
800
|
+
"Name",
|
|
801
|
+
"Type",
|
|
802
|
+
"Location",
|
|
803
|
+
"Status",
|
|
804
|
+
"IPv4",
|
|
805
|
+
"Disk",
|
|
806
|
+
"Vol GB",
|
|
807
|
+
"Bkp",
|
|
808
|
+
"Monthly",
|
|
809
|
+
], report.servers.map(inventoryServerRow));
|
|
810
|
+
appendTable(lines, "Volumes", ["ID", "Name", "Size", "Location", "Status", "Attached To", "Monthly"], report.volumes.map(inventoryVolumeRow));
|
|
811
|
+
appendTable(lines, "IP Addresses", ["ID", "Kind", "IP", "Location", "Assigned To", "Monthly"], report.ips.map(inventoryIpRow));
|
|
812
|
+
appendTable(lines, "Load Balancers", ["ID", "Name", "Type", "Location", "Targets", "Monthly"], report.loadBalancers.map(inventoryLoadBalancerRow));
|
|
813
|
+
appendTable(lines, "Snapshots/Backup Images", ["ID", "Name", "Type", "Size", "Monthly"], report.images.map(inventoryImageRow));
|
|
814
|
+
lines.push(heading("Other Resources"), `Networks: ${report.counts.networks}, Firewalls: ${report.counts.firewalls}, Certificates: ${report.counts.certificates}, SSH Keys: ${report.counts.sshKeys}, Placement Groups: ${report.counts.placementGroups}`, "");
|
|
815
|
+
const t = report.totals;
|
|
816
|
+
lines.push(heading("Estimated Monthly Cost"), `Servers: ${formatCost(t.servers)} ${report.currency}`, `Volumes: ${formatCost(t.volumes)} ${report.currency}`, `IP addresses: ${formatCost(t.ips)} ${report.currency}`, `Load balancers: ${formatCost(t.loadBalancers)} ${report.currency}`, `Images: ${formatCost(t.images)} ${report.currency}`, `TOTAL: ${formatCost(t.monthly)} ${report.currency} (incl. VAT, list prices)`, "", info(report.note), info("Dedicated (Robot) servers: run 'hctl robot server list' — contract-priced, not in this estimate."));
|
|
817
|
+
if (report.warnings.length > 0) {
|
|
818
|
+
lines.push("", heading(`Warnings (${report.warnings.length})`));
|
|
819
|
+
for (const w of report.warnings) {
|
|
820
|
+
lines.push(warning(w));
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return lines.join("\n");
|
|
824
|
+
}
|