sillydev 1.0.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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +277 -0
- data/exe/sillydev +6 -0
- data/lib/sillydev/cli.rb +295 -0
- data/lib/sillydev/client.rb +199 -0
- data/lib/sillydev/console.rb +1131 -0
- data/lib/sillydev/envelope.rb +84 -0
- data/lib/sillydev/errors.rb +160 -0
- data/lib/sillydev/mcp_server.rb +193 -0
- data/lib/sillydev/monitoring/aggregator.rb +107 -0
- data/lib/sillydev/monitoring/client.rb +674 -0
- data/lib/sillydev/monitoring/discordrb.rb +131 -0
- data/lib/sillydev/monitoring/http.rb +31 -0
- data/lib/sillydev/monitoring/rack.rb +114 -0
- data/lib/sillydev/monitoring/rails.rb +41 -0
- data/lib/sillydev/monitoring/runtime.rb +96 -0
- data/lib/sillydev/monitoring/serialize.rb +168 -0
- data/lib/sillydev/monitoring.rb +227 -0
- data/lib/sillydev/resources.rb +844 -0
- data/lib/sillydev/transport.rb +71 -0
- data/lib/sillydev/version.rb +5 -0
- data/lib/sillydev/websocket.rb +248 -0
- data/lib/sillydev.rb +20 -0
- metadata +67 -0
|
@@ -0,0 +1,844 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module SillyDev
|
|
6
|
+
# The panel API's endpoints, grouped as in the API docs.
|
|
7
|
+
#
|
|
8
|
+
# Each method names the operationId it calls in https://sillydev.co.uk/docs/panel/openapi.json.
|
|
9
|
+
# Every method returns plain hashes and arrays (string keys), with the {"object", "attributes"}
|
|
10
|
+
# envelopes removed. Methods named list (and activity) on paginated endpoints return an
|
|
11
|
+
# Enumerator that fetches the following pages as it goes; their *_page twins return a single
|
|
12
|
+
# SillyDev::Page, which also carries the list's meta.
|
|
13
|
+
module Resources
|
|
14
|
+
# Shared plumbing for the resource classes.
|
|
15
|
+
class Base
|
|
16
|
+
def initialize(client)
|
|
17
|
+
@client = client
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def inspect
|
|
21
|
+
"#<#{self.class.name}>"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def req(method, path, **kwargs)
|
|
27
|
+
@client.request(method, path, **kwargs)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def seg(value)
|
|
31
|
+
Client.segment(value)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def server_path(server)
|
|
35
|
+
"/servers/#{seg(server)}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def project_path(project)
|
|
39
|
+
"/monitoring/projects/#{seg(project)}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# A request body without the optional fields that were left as nil.
|
|
43
|
+
def body(**fields)
|
|
44
|
+
fields.compact.transform_keys(&:to_s)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Turns [["a", "b"]] or [{"from" => "a", "to" => "b"}] into [{"from" => "a", "to" => "b"}].
|
|
48
|
+
def pairs(values, first, second)
|
|
49
|
+
values.map do |value|
|
|
50
|
+
if value.is_a?(Hash)
|
|
51
|
+
{ first => value[first] || value[first.to_sym], second => value[second] || value[second.to_sym] }
|
|
52
|
+
else
|
|
53
|
+
a, b = value
|
|
54
|
+
{ first => a, second => b }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# The body with its meta folded in: {key => body, ...meta}.
|
|
60
|
+
def with_meta(key, raw)
|
|
61
|
+
return { key => Envelope.unwrap(raw) } unless raw.is_a?(Hash)
|
|
62
|
+
|
|
63
|
+
out = { key => Envelope.unwrap(raw.except("meta")) }
|
|
64
|
+
out.merge!(Envelope.unwrap(raw["meta"])) if raw["meta"].is_a?(Hash)
|
|
65
|
+
out
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Servers, plus power and console (tags Servers and Power and console).
|
|
70
|
+
class Servers < Base
|
|
71
|
+
# Every server the key can reach, fetching page after page. operationId: listServers
|
|
72
|
+
def list(type: nil, search: nil, uuid: nil, name: nil, description: nil, external_id: nil, include: nil, per_page: nil)
|
|
73
|
+
@client.paginate("/", server_filters(type, search, uuid, name, description, external_id, include, per_page))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# One page of servers. page.meta["pending_orders"] lists paid servers not built yet. operationId: listServers
|
|
77
|
+
def list_page(page: 1, per_page: nil, type: nil, search: nil, uuid: nil, name: nil, description: nil,
|
|
78
|
+
external_id: nil, include: nil)
|
|
79
|
+
params = server_filters(type, search, uuid, name, description, external_id, include, per_page)
|
|
80
|
+
@client.page("/", params.merge(page: page))
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# One server's details. operationId: getServer
|
|
84
|
+
def get(server, include: nil)
|
|
85
|
+
req("GET", server_path(server), params: { include: include })
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Delete a server you own. name must be the server's current name, as a confirmation. operationId: deleteServer
|
|
89
|
+
def delete(server, name:)
|
|
90
|
+
req("DELETE", server_path(server), json: { "name" => name })
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Live state and resource use of one server. operationId: getServerResources
|
|
94
|
+
def resources(server)
|
|
95
|
+
req("GET", "#{server_path(server)}/resources")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Waits until the server is in state, checking its live state every interval seconds.
|
|
99
|
+
#
|
|
100
|
+
# Returns the server's resources from the check that found it in that state. Raises
|
|
101
|
+
# SillyDev::WaitTimeoutError if it isn't after timeout seconds. For example
|
|
102
|
+
# servers.start(id) then servers.wait_for_state(id). operationId: getServerResources
|
|
103
|
+
def wait_for_state(server, state = "running", timeout: 120.0, interval: 2.0)
|
|
104
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
105
|
+
loop do
|
|
106
|
+
current = resources(server)
|
|
107
|
+
return current if current.is_a?(Hash) && current["current_state"] == state
|
|
108
|
+
|
|
109
|
+
now_state = current.is_a?(Hash) ? current["current_state"] : nil
|
|
110
|
+
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
111
|
+
if remaining <= 0
|
|
112
|
+
raise WaitTimeoutError.new("server #{server} is #{now_state}, not #{state}, after #{timeout} seconds",
|
|
113
|
+
state: now_state)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
@client.sleeper.call([interval, remaining].min)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Live resource use of up to 100 servers, keyed by UUID (full UUIDs only). operationId: listServerResources
|
|
121
|
+
def resources_many(servers)
|
|
122
|
+
req("GET", "/resources", params: { servers: Array(servers).join(",") })
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# The server's activity log, fetching page after page. operationId: listServerActivity
|
|
126
|
+
def activity(server, event: nil, sort: nil, include: nil, per_page: nil)
|
|
127
|
+
@client.paginate("#{server_path(server)}/activity",
|
|
128
|
+
{ "filter[event]" => event, sort: sort, include: include, per_page: per_page })
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# One page of the server's activity log. operationId: listServerActivity
|
|
132
|
+
def activity_page(server, page: 1, per_page: nil, event: nil, sort: nil, include: nil)
|
|
133
|
+
@client.page("#{server_path(server)}/activity",
|
|
134
|
+
{ page: page, per_page: per_page, "filter[event]" => event, sort: sort, include: include })
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Every server permission there is, grouped, with descriptions. operationId: listPermissions
|
|
138
|
+
def permissions
|
|
139
|
+
req("GET", "/permissions")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Send "start", "stop", "restart" or "kill". operationId: sendPowerSignal
|
|
143
|
+
def power(server, signal)
|
|
144
|
+
req("POST", "#{server_path(server)}/power", json: { "signal" => signal.to_s })
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Shortcut for power(server, "start"). operationId: sendPowerSignal
|
|
148
|
+
def start(server) = power(server, "start")
|
|
149
|
+
|
|
150
|
+
# Shortcut for power(server, "stop"). operationId: sendPowerSignal
|
|
151
|
+
def stop(server) = power(server, "stop")
|
|
152
|
+
|
|
153
|
+
# Shortcut for power(server, "restart"). operationId: sendPowerSignal
|
|
154
|
+
def restart(server) = power(server, "restart")
|
|
155
|
+
|
|
156
|
+
# Shortcut for power(server, "kill"). operationId: sendPowerSignal
|
|
157
|
+
def kill(server) = power(server, "kill")
|
|
158
|
+
|
|
159
|
+
# Send a command to the server's console. operationId: sendCommand
|
|
160
|
+
def command(server, command)
|
|
161
|
+
req("POST", "#{server_path(server)}/command", json: { "command" => command })
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Upload console output to a share link, with secrets hidden; returns the URL. operationId: shareLogs
|
|
165
|
+
def share_logs(server, content)
|
|
166
|
+
req("POST", "#{server_path(server)}/logs/share", json: { "content" => content })["url"].to_s
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# A token and the addresses of the live console: {"token", "socket", "sessions"}. operationId: getWebsocketCredentials
|
|
170
|
+
def websocket(server)
|
|
171
|
+
req("GET", "#{server_path(server)}/websocket")["data"]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Open a console session that the panel reads for you; returns the session ID. operationId: openConsoleRelay
|
|
175
|
+
def open_relay(server)
|
|
176
|
+
(req("POST", "#{server_path(server)}/websocket/relay")["data"] || {})["session"].to_s
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# The relay session's messages after the one numbered after: {"events", "missed", "more"}. operationId: pollConsoleRelay
|
|
180
|
+
def poll_relay(server, session, after: nil)
|
|
181
|
+
req("GET", "#{server_path(server)}/websocket/relay/#{seg(session)}", params: { after: after })["data"]
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Send a websocket message ("send command", "set state"...) through a relay session. operationId: sendConsoleRelay
|
|
185
|
+
def send_relay(server, session, event, *args)
|
|
186
|
+
req("POST", "#{server_path(server)}/websocket/relay/#{seg(session)}/send",
|
|
187
|
+
json: { "event" => event, "args" => args.map(&:to_s) })
|
|
188
|
+
nil
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# End a relay session. operationId: closeConsoleRelay
|
|
192
|
+
def close_relay(server, session)
|
|
193
|
+
req("DELETE", "#{server_path(server)}/websocket/relay/#{seg(session)}")
|
|
194
|
+
nil
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
|
|
199
|
+
def server_filters(type, search, uuid, name, description, external_id, include, per_page)
|
|
200
|
+
{
|
|
201
|
+
type: type,
|
|
202
|
+
"filter[*]" => search,
|
|
203
|
+
"filter[uuid]" => uuid,
|
|
204
|
+
"filter[name]" => name,
|
|
205
|
+
"filter[description]" => description,
|
|
206
|
+
"filter[external_id]" => external_id,
|
|
207
|
+
include: include,
|
|
208
|
+
per_page: per_page,
|
|
209
|
+
}
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Rename, reinstall, change the Docker image and the status badge (tag Settings).
|
|
214
|
+
class Settings < Base
|
|
215
|
+
# Rename a server and optionally change its description. operationId: renameServer
|
|
216
|
+
def rename(server, name, description: nil)
|
|
217
|
+
req("POST", "#{server_path(server)}/settings/rename", json: body(name: name, description: description))
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Run the server's install script again. operationId: reinstallServer
|
|
221
|
+
def reinstall(server)
|
|
222
|
+
req("POST", "#{server_path(server)}/settings/reinstall")
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Switch to another of the egg's Docker images. operationId: setDockerImage
|
|
226
|
+
def set_docker_image(server, docker_image)
|
|
227
|
+
req("PUT", "#{server_path(server)}/settings/docker-image", json: { "docker_image" => docker_image })
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# The public status badge: {"enabled", "show_name", "url", "link", "markdown"}. operationId: getStatusBadge
|
|
231
|
+
def badge(server)
|
|
232
|
+
req("GET", "#{server_path(server)}/settings/badge")
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Turn the status badge on, or change whether it shows the server's name. operationId: updateStatusBadge
|
|
236
|
+
def enable_badge(server, show_name: nil)
|
|
237
|
+
req("PUT", "#{server_path(server)}/settings/badge", json: body(show_name: show_name))
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Give the status badge a new address; the old one stops working. operationId: regenerateStatusBadge
|
|
241
|
+
def regenerate_badge(server)
|
|
242
|
+
req("POST", "#{server_path(server)}/settings/badge/regenerate")
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Turn the status badge off and forget its address. operationId: deleteStatusBadge
|
|
246
|
+
def disable_badge(server)
|
|
247
|
+
req("DELETE", "#{server_path(server)}/settings/badge")
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# The startup command and variables (tag Startup).
|
|
252
|
+
class Startup < Base
|
|
253
|
+
# {"variables" => [...], "startup_command", "raw_startup_command", "docker_images", ...}. operationId: getStartup
|
|
254
|
+
def get(server)
|
|
255
|
+
with_meta("variables", req("GET", "#{server_path(server)}/startup", raw: true))
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Set one startup variable by its env_variable; returns {"variable" => {...}, "startup_command", ...}.
|
|
259
|
+
# operationId: updateStartupVariable
|
|
260
|
+
def set_variable(server, key, value)
|
|
261
|
+
with_meta("variable",
|
|
262
|
+
req("PUT", "#{server_path(server)}/startup/variable", json: { "key" => key, "value" => value }, raw: true))
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Browse and change a server's files (tag Files).
|
|
267
|
+
class Files < Base
|
|
268
|
+
# The files and folders in one directory (not paginated). operationId: listFiles
|
|
269
|
+
def list(server, directory = "/")
|
|
270
|
+
req("GET", "#{server_path(server)}/files/list", params: { directory: directory })
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# A file's contents as text (files up to 4 MiB). operationId: getFileContents
|
|
274
|
+
def read(server, file)
|
|
275
|
+
req("GET", "#{server_path(server)}/files/contents", params: { file: file }, text: true)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# A single-use download link for a file, valid for 15 minutes. operationId: getFileDownloadUrl
|
|
279
|
+
def download_url(server, file)
|
|
280
|
+
req("GET", "#{server_path(server)}/files/download", params: { file: file })["url"].to_s
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Create or overwrite a file. A String in Encoding::BINARY is sent as binary, any other as text. operationId: writeFile
|
|
284
|
+
def write(server, file, content)
|
|
285
|
+
req("POST", "#{server_path(server)}/files/write", params: { file: file }, content: content.to_s)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Rename or move files: files is [["old", "new"]] or [{"from" => ..., "to" => ...}]. operationId: renameFiles
|
|
289
|
+
def rename(server, files, root: nil)
|
|
290
|
+
req("PUT", "#{server_path(server)}/files/rename", json: { "root" => root, "files" => pairs(files, "from", "to") })
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Copy a file next to itself (main.rb becomes "main copy.rb"). operationId: copyFile
|
|
294
|
+
def copy(server, location)
|
|
295
|
+
req("POST", "#{server_path(server)}/files/copy", json: { "location" => location })
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Pack files into an archive in root; returns the archive's file object. operationId: compressFiles
|
|
299
|
+
def compress(server, files, root: nil)
|
|
300
|
+
req("POST", "#{server_path(server)}/files/compress", json: body(root: root, files: Array(files)))
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Unpack an archive into root. operationId: decompressFile
|
|
304
|
+
def decompress(server, file, root: nil)
|
|
305
|
+
req("POST", "#{server_path(server)}/files/decompress", json: body(root: root, file: file))
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Delete files and folders. operationId: deleteFiles
|
|
309
|
+
def delete(server, files, root: nil)
|
|
310
|
+
req("POST", "#{server_path(server)}/files/delete", json: { "root" => root, "files" => Array(files) })
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Create a folder in root. operationId: createFolder
|
|
314
|
+
def create_folder(server, name, root: nil)
|
|
315
|
+
req("POST", "#{server_path(server)}/files/create-folder", json: body(root: root, name: name))
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Change permissions: files is [["main.rb", "644"]] or [{"file" => ..., "mode" => ...}]. operationId: chmodFiles
|
|
319
|
+
def chmod(server, files, root: nil)
|
|
320
|
+
req("POST", "#{server_path(server)}/files/chmod", json: { "root" => root, "files" => pairs(files, "file", "mode") })
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Have the node download a file from a URL into the server. operationId: pullFile
|
|
324
|
+
def pull(server, url, directory: nil, filename: nil, use_header: nil, foreground: nil)
|
|
325
|
+
req("POST", "#{server_path(server)}/files/pull",
|
|
326
|
+
json: body(url: url, directory: directory, filename: filename, use_header: use_header, foreground: foreground))
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# A single-use upload link on the node, valid for 15 minutes. operationId: getFileUploadUrl
|
|
330
|
+
def upload_url(server)
|
|
331
|
+
req("GET", "#{server_path(server)}/files/upload")["url"].to_s
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Whether the files are managed by a linked Git repository. operationId: getFileGitNotice
|
|
335
|
+
def git_notice(server)
|
|
336
|
+
req("GET", "#{server_path(server)}/files/git")
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Backups of a server's files (tag Backups).
|
|
341
|
+
class Backups < Base
|
|
342
|
+
# Every backup, fetching page after page. operationId: listBackups
|
|
343
|
+
def list(server, per_page: nil)
|
|
344
|
+
@client.paginate("#{server_path(server)}/backups", { per_page: per_page })
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# One page of backups; page.meta["backup_count"] counts those towards the limit. operationId: listBackups
|
|
348
|
+
def list_page(server, page: 1, per_page: nil)
|
|
349
|
+
@client.page("#{server_path(server)}/backups", { page: page, per_page: per_page })
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# Start a backup. ignored is paths to leave out, one per line or as an array. operationId: createBackup
|
|
353
|
+
def create(server, name: nil, is_locked: nil, ignored: nil)
|
|
354
|
+
ignored = ignored.join("\n") if ignored.is_a?(Array)
|
|
355
|
+
req("POST", "#{server_path(server)}/backups", json: body(name: name, is_locked: is_locked, ignored: ignored))
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# One backup. operationId: getBackup
|
|
359
|
+
def get(server, backup)
|
|
360
|
+
req("GET", "#{server_path(server)}/backups/#{seg(backup)}")
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Delete an unlocked backup. operationId: deleteBackup
|
|
364
|
+
def delete(server, backup)
|
|
365
|
+
req("DELETE", "#{server_path(server)}/backups/#{seg(backup)}")
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# A single-use download link for a backup, valid for 15 minutes. operationId: getBackupDownloadUrl
|
|
369
|
+
def download_url(server, backup)
|
|
370
|
+
req("GET", "#{server_path(server)}/backups/#{seg(backup)}/download")["url"].to_s
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# Lock an unlocked backup or unlock a locked one. operationId: toggleBackupLock
|
|
374
|
+
def toggle_lock(server, backup)
|
|
375
|
+
req("POST", "#{server_path(server)}/backups/#{seg(backup)}/lock")
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# Restore a backup; truncate: true deletes every file first. operationId: restoreBackup
|
|
379
|
+
def restore(server, backup, truncate: false)
|
|
380
|
+
req("POST", "#{server_path(server)}/backups/#{seg(backup)}/restore", json: { "truncate" => truncate })
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# Schedules and their tasks (tag Schedules).
|
|
385
|
+
class Schedules < Base
|
|
386
|
+
# Every schedule on the server, with its tasks. operationId: listSchedules
|
|
387
|
+
def list(server)
|
|
388
|
+
req("GET", "#{server_path(server)}/schedules")
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
# Create a schedule from a cron timetable. operationId: createSchedule
|
|
392
|
+
def create(server, name:, minute:, hour:, day_of_month:, month:, day_of_week:, is_active: nil, only_when_online: nil)
|
|
393
|
+
req("POST", "#{server_path(server)}/schedules",
|
|
394
|
+
json: body(name: name, minute: minute, hour: hour, day_of_month: day_of_month, month: month,
|
|
395
|
+
day_of_week: day_of_week, is_active: is_active, only_when_online: only_when_online))
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# One schedule with its tasks. operationId: getSchedule
|
|
399
|
+
def get(server, schedule)
|
|
400
|
+
req("GET", "#{server_path(server)}/schedules/#{seg(schedule)}")
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
# Replace a schedule's name and timetable. operationId: updateSchedule
|
|
404
|
+
def update(server, schedule, name:, minute:, hour:, day_of_month:, month:, day_of_week:, is_active: nil,
|
|
405
|
+
only_when_online: nil)
|
|
406
|
+
req("POST", "#{server_path(server)}/schedules/#{seg(schedule)}",
|
|
407
|
+
json: body(name: name, minute: minute, hour: hour, day_of_month: day_of_month, month: month,
|
|
408
|
+
day_of_week: day_of_week, is_active: is_active, only_when_online: only_when_online))
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Delete a schedule and its tasks. operationId: deleteSchedule
|
|
412
|
+
def delete(server, schedule)
|
|
413
|
+
req("DELETE", "#{server_path(server)}/schedules/#{seg(schedule)}")
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# Run a schedule now. operationId: runSchedule
|
|
417
|
+
def run(server, schedule)
|
|
418
|
+
req("POST", "#{server_path(server)}/schedules/#{seg(schedule)}/execute")
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# Add a task ("command", "power" or "backup") to a schedule. operationId: createScheduleTask
|
|
422
|
+
def create_task(server, schedule, action:, time_offset:, payload: nil, sequence_id: nil, continue_on_failure: nil)
|
|
423
|
+
req("POST", "#{server_path(server)}/schedules/#{seg(schedule)}/tasks",
|
|
424
|
+
json: body(action: action, payload: payload, time_offset: time_offset, sequence_id: sequence_id,
|
|
425
|
+
continue_on_failure: continue_on_failure))
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# Change a task. operationId: updateScheduleTask
|
|
429
|
+
def update_task(server, schedule, task, action:, time_offset:, payload: nil, sequence_id: nil,
|
|
430
|
+
continue_on_failure: nil)
|
|
431
|
+
req("POST", "#{server_path(server)}/schedules/#{seg(schedule)}/tasks/#{seg(task)}",
|
|
432
|
+
json: body(action: action, payload: payload, time_offset: time_offset, sequence_id: sequence_id,
|
|
433
|
+
continue_on_failure: continue_on_failure))
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# Remove a task from a schedule. operationId: deleteScheduleTask
|
|
437
|
+
def delete_task(server, schedule, task)
|
|
438
|
+
req("DELETE", "#{server_path(server)}/schedules/#{seg(schedule)}/tasks/#{seg(task)}")
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
# The server's allocations, its IP and port pairs (tag Network).
|
|
443
|
+
class Network < Base
|
|
444
|
+
# Every allocation on the server. operationId: listAllocations
|
|
445
|
+
def list(server)
|
|
446
|
+
req("GET", "#{server_path(server)}/network/allocations")
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
# Add a port, where an administrator has allowed it. operationId: createAllocation
|
|
450
|
+
def create(server)
|
|
451
|
+
req("POST", "#{server_path(server)}/network/allocations")
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
# Set or clear (nil) an allocation's notes. operationId: updateAllocationNotes
|
|
455
|
+
def set_notes(server, allocation, notes)
|
|
456
|
+
req("POST", "#{server_path(server)}/network/allocations/#{seg(allocation)}", json: { "notes" => notes })
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
# Remove an allocation that is not the primary one. operationId: deleteAllocation
|
|
460
|
+
def delete(server, allocation)
|
|
461
|
+
req("DELETE", "#{server_path(server)}/network/allocations/#{seg(allocation)}")
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
# Make an allocation the primary one. operationId: setPrimaryAllocation
|
|
465
|
+
def set_primary(server, allocation)
|
|
466
|
+
req("POST", "#{server_path(server)}/network/allocations/#{seg(allocation)}/primary")
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
# Other accounts with access to the server (tag Subusers).
|
|
471
|
+
class Subusers < Base
|
|
472
|
+
# Every subuser of the server. operationId: listSubusers
|
|
473
|
+
def list(server)
|
|
474
|
+
req("GET", "#{server_path(server)}/users")
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
# Invite an account by email with a list of permissions. operationId: createSubuser
|
|
478
|
+
def create(server, email, permissions)
|
|
479
|
+
req("POST", "#{server_path(server)}/users", json: { "email" => email, "permissions" => Array(permissions) })
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
# One subuser, by their UUID. operationId: getSubuser
|
|
483
|
+
def get(server, user)
|
|
484
|
+
req("GET", "#{server_path(server)}/users/#{seg(user)}")
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
# Replace a subuser's permissions. operationId: updateSubuser
|
|
488
|
+
def update(server, user, permissions)
|
|
489
|
+
req("POST", "#{server_path(server)}/users/#{seg(user)}", json: { "permissions" => Array(permissions) })
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# Remove a subuser. operationId: deleteSubuser
|
|
493
|
+
def delete(server, user)
|
|
494
|
+
req("DELETE", "#{server_path(server)}/users/#{seg(user)}")
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
# Deploy a Git repository into the server's files (tag Git deploys).
|
|
499
|
+
class Git < Base
|
|
500
|
+
# The linked repository, its settings and recent deployments. operationId: getGitDeploys
|
|
501
|
+
def get(server)
|
|
502
|
+
req("GET", "#{server_path(server)}/git")
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# Link a repository ("github", "gitlab" or "url" source), or change the link's settings. operationId: linkGitRepository
|
|
506
|
+
def link(server, source:, repository:, branch:, installation: nil, directory: nil, auto_deploy: nil,
|
|
507
|
+
restart_on_deploy: nil, start_if_offline: nil, apply_config: nil, sync_startup: nil, write_config: nil)
|
|
508
|
+
req("PUT", "#{server_path(server)}/git",
|
|
509
|
+
json: body(source: source, installation: installation, repository: repository, branch: branch,
|
|
510
|
+
directory: directory, auto_deploy: auto_deploy, restart_on_deploy: restart_on_deploy,
|
|
511
|
+
start_if_offline: start_if_offline, apply_config: apply_config, sync_startup: sync_startup,
|
|
512
|
+
write_config: write_config))
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
# Unlink the repository; the files stay. operationId: unlinkGitRepository
|
|
516
|
+
def unlink(server, delete_config: nil)
|
|
517
|
+
req("DELETE", "#{server_path(server)}/git", params: { delete_config: delete_config })
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
# Repositories the connected GitHub or GitLab account can deploy: {"data", "errors"}. operationId: listGitRepositories
|
|
521
|
+
def repositories(server, provider: nil)
|
|
522
|
+
req("GET", "#{server_path(server)}/git/repositories", params: { provider: provider })
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# Deploy the linked branch now; returns the queued deployment. operationId: deployGitRepository
|
|
526
|
+
def deploy(server)
|
|
527
|
+
req("POST", "#{server_path(server)}/git/deploy")
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
# Cancel a queued or running deployment. operationId: cancelGitDeployment
|
|
531
|
+
def cancel_deployment(server, deployment)
|
|
532
|
+
req("POST", "#{server_path(server)}/git/deployments/#{seg(deployment)}/cancel")
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
# A sillydev.conf written from the server's current settings. operationId: getGitConfigTemplate
|
|
536
|
+
def config_template(server)
|
|
537
|
+
req("GET", "#{server_path(server)}/git/config")
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
# Commit the server's settings to sillydev.conf in the repository. operationId: syncGitConfig
|
|
541
|
+
def sync_config(server)
|
|
542
|
+
req("POST", "#{server_path(server)}/git/config")
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
# The repository's sillydev.conf as the panel's editor shows it. operationId: getGitConfigEditor
|
|
546
|
+
def config_editor(server)
|
|
547
|
+
req("GET", "#{server_path(server)}/git/config/editor")
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
# Commit a new sillydev.conf (mode is "form" or "raw"). operationId: saveGitConfigEditor
|
|
551
|
+
def save_config_editor(server, mode:, sha: nil, raw: nil, variables: nil, image: nil)
|
|
552
|
+
req("PUT", "#{server_path(server)}/git/config/editor",
|
|
553
|
+
json: body(mode: mode, sha: sha, raw: raw, variables: variables, image: image))
|
|
554
|
+
end
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
# The account the key belongs to (tag Account).
|
|
558
|
+
class Account < Base
|
|
559
|
+
# The account's details. operationId: getAccount
|
|
560
|
+
def get
|
|
561
|
+
req("GET", "/account")
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
# The account's activity log, fetching page after page. operationId: listAccountActivity
|
|
565
|
+
def activity(event: nil, sort: nil, include: nil, per_page: nil)
|
|
566
|
+
@client.paginate("/account/activity",
|
|
567
|
+
{ "filter[event]" => event, sort: sort, include: include, per_page: per_page })
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
# One page of the account's activity log. operationId: listAccountActivity
|
|
571
|
+
def activity_page(page: 1, per_page: nil, event: nil, sort: nil, include: nil)
|
|
572
|
+
@client.page("/account/activity",
|
|
573
|
+
{ page: page, per_page: per_page, "filter[event]" => event, sort: sort, include: include })
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
# Create free servers (tag Server creation).
|
|
578
|
+
class ServerCreation < Base
|
|
579
|
+
# How many free servers the account may create, and the runtimes on offer. operationId: getServerCreationOptions
|
|
580
|
+
def options
|
|
581
|
+
req("GET", "/server-creation")
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
# Create a free server ("nodejs", "python" or "bun" runtime). operationId: createServer
|
|
585
|
+
def create(name:, runtime:, description: nil)
|
|
586
|
+
req("POST", "/server-creation", json: body(name: name, description: description, runtime: runtime))
|
|
587
|
+
end
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
# The account's free MariaDB and Postgres databases (tag Databases).
|
|
591
|
+
class Databases < Base
|
|
592
|
+
# {"limit", "owned", "can_create", "quota_bytes", "versions", "databases"}. operationId: listDatabases
|
|
593
|
+
def list
|
|
594
|
+
req("GET", "/databases")
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
# Create a database; the result holds its password. operationId: createDatabase
|
|
598
|
+
def create(label:, version_id:)
|
|
599
|
+
req("POST", "/databases", json: { "label" => label, "version_id" => version_id })
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
# Give the database a new password. operationId: rotateDatabasePassword
|
|
603
|
+
def rotate_password(database)
|
|
604
|
+
req("POST", "/databases/#{seg(database)}/rotate-password")
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
# Move the database to another version. operationId: moveDatabase
|
|
608
|
+
def move(database, version_id:)
|
|
609
|
+
req("POST", "/databases/#{seg(database)}/move", json: { "version_id" => version_id })
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
# Go back to the version the database was moved from. operationId: undoDatabaseMove
|
|
613
|
+
def undo_move(database)
|
|
614
|
+
req("POST", "/databases/#{seg(database)}/undo-move")
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
# Keep a move and drop the old copy. operationId: keepDatabaseMove
|
|
618
|
+
def keep_move(database)
|
|
619
|
+
req("POST", "/databases/#{seg(database)}/keep-move")
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
# Delete a database and everything in it. operationId: deleteDatabase
|
|
623
|
+
def delete(database)
|
|
624
|
+
req("DELETE", "/databases/#{seg(database)}")
|
|
625
|
+
end
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
# Paid servers and databases (tag Billing). Prices are in pence.
|
|
629
|
+
class Billing < Base
|
|
630
|
+
# The plan, the account's subscriptions and its saved card. operationId: getBilling
|
|
631
|
+
def get
|
|
632
|
+
req("GET", "/billing")
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
# Order a new paid server (name: and runtime:) or upgrade a free one (server_uuid:).
|
|
636
|
+
#
|
|
637
|
+
# An Idempotency-Key is always sent, so a retried request never buys twice: pass your own
|
|
638
|
+
# idempotency_key: to make retries across runs safe too. operationId: createCheckout
|
|
639
|
+
def checkout(name: nil, runtime: nil, description: nil, server_uuid: nil, use_saved_card: nil,
|
|
640
|
+
promotion_code: nil, plan: nil, idempotency_key: nil)
|
|
641
|
+
req("POST", "/billing/checkout",
|
|
642
|
+
json: body(server_uuid: server_uuid, name: name, description: description, runtime: runtime,
|
|
643
|
+
use_saved_card: use_saved_card, promotion_code: promotion_code, plan: plan),
|
|
644
|
+
headers: { "Idempotency-Key" => idempotency_key || SecureRandom.uuid })
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
# Order a paid database (plan:, label: and version_id:) or put a free one on a paid plan
|
|
648
|
+
# (plan: and database_id:). An Idempotency-Key is always sent. operationId: createDatabaseCheckout
|
|
649
|
+
def database_checkout(plan:, database_id: nil, label: nil, version_id: nil, use_saved_card: nil,
|
|
650
|
+
promotion_code: nil, idempotency_key: nil)
|
|
651
|
+
req("POST", "/billing/databases/checkout",
|
|
652
|
+
json: body(plan: plan, database_id: database_id, label: label, version_id: version_id,
|
|
653
|
+
use_saved_card: use_saved_card, promotion_code: promotion_code),
|
|
654
|
+
headers: { "Idempotency-Key" => idempotency_key || SecureRandom.uuid })
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
# Change a paid database's plan, or pass "free" to end it. operationId: changeDatabasePlan
|
|
658
|
+
def change_database_plan(database, plan)
|
|
659
|
+
req("POST", "/billing/databases/#{seg(database)}/plan", json: { "plan" => plan })
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
# Undo a waiting move to a smaller plan, or to free. operationId: keepDatabasePlan
|
|
663
|
+
def keep_database_plan(database)
|
|
664
|
+
req("POST", "/billing/databases/#{seg(database)}/plan/keep")
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
# The credit balance and one page (25 entries, newest first) of its activity. view: is
|
|
668
|
+
# "history" (the default) or "pending". operationId: getAccountCredit
|
|
669
|
+
def credit(view: nil, page: nil)
|
|
670
|
+
req("GET", "/billing/credit", params: { view: view, page: page })
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
# Every entry of the credit activity (or pending rewards), fetching each page as it is reached.
|
|
674
|
+
def credit_entries(view = "history")
|
|
675
|
+
Enumerator.new do |yielder|
|
|
676
|
+
number = 1
|
|
677
|
+
loop do
|
|
678
|
+
result = credit(view: view, page: number)
|
|
679
|
+
entries = result["data"] || []
|
|
680
|
+
entries.each { |entry| yielder << entry }
|
|
681
|
+
pages = ((result["pagination"] || {})["total_pages"] || 1).to_i
|
|
682
|
+
break if entries.empty? || number >= pages
|
|
683
|
+
|
|
684
|
+
number += 1
|
|
685
|
+
end
|
|
686
|
+
end
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
# Move a paid server to another paid plan. A smaller one waits for the next bill unless
|
|
690
|
+
# when: "now" (refund_to: "card" or "credit"). operationId: changeServerPlan
|
|
691
|
+
def change_server_plan(server_uuid, plan, when: nil, refund_to: nil)
|
|
692
|
+
req("POST", "/billing/subscriptions/#{seg(server_uuid)}/plan",
|
|
693
|
+
json: body(plan: plan, when: binding.local_variable_get(:when), refund_to: refund_to))
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
# Undo a waiting move to a smaller paid plan. operationId: keepServerPlan
|
|
697
|
+
def keep_server_plan(server_uuid)
|
|
698
|
+
req("POST", "/billing/subscriptions/#{seg(server_uuid)}/plan/keep")
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
# Cancel an order that has not been paid. operationId: cancelOrder
|
|
702
|
+
def cancel_order(order_uuid)
|
|
703
|
+
req("DELETE", "/billing/orders/#{seg(order_uuid)}")
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
# Cancel a server's plan at the end of the period. operationId: cancelSubscription
|
|
707
|
+
def cancel_subscription(server_uuid)
|
|
708
|
+
req("POST", "/billing/subscriptions/#{seg(server_uuid)}/cancel")
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
# Undo a cancellation before the period ends. operationId: resumeSubscription
|
|
712
|
+
def resume_subscription(server_uuid)
|
|
713
|
+
req("POST", "/billing/subscriptions/#{seg(server_uuid)}/resume")
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
# Move the server back to the free tier now. operationId: cancelSubscriptionImmediately
|
|
717
|
+
def cancel_subscription_immediately(server_uuid)
|
|
718
|
+
req("POST", "/billing/subscriptions/#{seg(server_uuid)}/cancel-immediately")
|
|
719
|
+
end
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
# Application monitoring projects, keys, issues and events (tag Monitoring). This manages
|
|
723
|
+
# projects; it never sends telemetry.
|
|
724
|
+
class Monitoring < Base
|
|
725
|
+
# The account's monitoring projects. operationId: listMonitoringProjects
|
|
726
|
+
def list_projects
|
|
727
|
+
req("GET", "/monitoring/projects")
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
# Create a monitoring project. operationId: createMonitoringProject
|
|
731
|
+
def create_project(name:)
|
|
732
|
+
req("POST", "/monitoring/projects", json: { "name" => name })
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
# One project, by its UUID. operationId: getMonitoringProject
|
|
736
|
+
def get_project(project)
|
|
737
|
+
req("GET", project_path(project))
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
# Rename a project, set the origins its browser keys work on, or change its alerts. operationId: updateMonitoringProject
|
|
741
|
+
def update_project(project, name: nil, allowed_origins: nil, alerts: nil)
|
|
742
|
+
req("PATCH", project_path(project),
|
|
743
|
+
json: body(name: name, allowed_origins: allowed_origins&.to_a, alerts: alerts&.to_h))
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
# Delete a project and all of its data. operationId: deleteMonitoringProject
|
|
747
|
+
def delete_project(project)
|
|
748
|
+
req("DELETE", project_path(project))
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
# The project's keys (never their secrets). operationId: listMonitoringKeys
|
|
752
|
+
def list_keys(project)
|
|
753
|
+
req("GET", "#{project_path(project)}/keys")
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
# Create a "server" (sdevm_) or "browser" (sdevb_) key. The result's "secret_key" is the key
|
|
757
|
+
# itself, which is never shown again. operationId: createMonitoringKey
|
|
758
|
+
def create_key(project, name:, kind: "server")
|
|
759
|
+
raw = req("POST", "#{project_path(project)}/keys", json: { "name" => name, "kind" => kind }, raw: true)
|
|
760
|
+
return Envelope.unwrap(raw) unless raw.is_a?(Hash)
|
|
761
|
+
|
|
762
|
+
out = Envelope.unwrap(raw.except("meta"))
|
|
763
|
+
out["secret_key"] = raw["meta"]["secret_key"] if raw["meta"].is_a?(Hash) && raw["meta"].key?("secret_key")
|
|
764
|
+
out
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
# Revoke a key at once. operationId: deleteMonitoringKey
|
|
768
|
+
def delete_key(project, key)
|
|
769
|
+
req("DELETE", "#{project_path(project)}/keys/#{seg(key)}")
|
|
770
|
+
end
|
|
771
|
+
|
|
772
|
+
# Grouped errors: {"data" => [...], "pagination" => {...}}. operationId: listMonitoringIssues
|
|
773
|
+
def list_issues(project, status: nil, environment: nil, release: nil, q: nil, sort: nil, page: nil, per_page: nil)
|
|
774
|
+
req("GET", "#{project_path(project)}/issues",
|
|
775
|
+
params: { status: status, environment: environment, release: release, q: q, sort: sort, page: page,
|
|
776
|
+
per_page: per_page })
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
# One issue with its hourly history and latest occurrence. operationId: getMonitoringIssue
|
|
780
|
+
def get_issue(project, fingerprint)
|
|
781
|
+
req("GET", "#{project_path(project)}/issues/#{seg(fingerprint)}")
|
|
782
|
+
end
|
|
783
|
+
|
|
784
|
+
# Mark an issue "resolved", "ignored" or "unresolved". operationId: updateMonitoringIssue
|
|
785
|
+
def update_issue(project, fingerprint, status:)
|
|
786
|
+
req("PATCH", "#{project_path(project)}/issues/#{seg(fingerprint)}", json: { "status" => status })
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
# Stored occurrences of an issue, newest first. operationId: listMonitoringIssueOccurrences
|
|
790
|
+
def list_issue_occurrences(project, fingerprint, page: nil, per_page: nil)
|
|
791
|
+
req("GET", "#{project_path(project)}/issues/#{seg(fingerprint)}/occurrences",
|
|
792
|
+
params: { page: page, per_page: per_page })
|
|
793
|
+
end
|
|
794
|
+
|
|
795
|
+
# Events, newest first; pass the answer's "next" as before: for older ones. operationId: listMonitoringEvents
|
|
796
|
+
def list_events(project, range: nil, from: nil, to: nil, name: nil, before: nil, limit: nil)
|
|
797
|
+
req("GET", "#{project_path(project)}/events",
|
|
798
|
+
params: { range: range, from: from, to: to, name: name, before: before, limit: limit })
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
# Series, daily quotas and what was dropped today. operationId: getMonitoringUsage
|
|
802
|
+
def usage(project)
|
|
803
|
+
req("GET", "#{project_path(project)}/usage")
|
|
804
|
+
end
|
|
805
|
+
end
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
# The panel API: client.panel.servers, client.panel.files and the rest.
|
|
809
|
+
class Panel
|
|
810
|
+
attr_reader :servers, :settings, :startup, :files, :backups, :schedules, :network, :subusers, :git,
|
|
811
|
+
:account, :server_creation, :databases, :billing, :monitoring
|
|
812
|
+
|
|
813
|
+
def initialize(client)
|
|
814
|
+
@client = client
|
|
815
|
+
@servers = Resources::Servers.new(client)
|
|
816
|
+
@settings = Resources::Settings.new(client)
|
|
817
|
+
@startup = Resources::Startup.new(client)
|
|
818
|
+
@files = Resources::Files.new(client)
|
|
819
|
+
@backups = Resources::Backups.new(client)
|
|
820
|
+
@schedules = Resources::Schedules.new(client)
|
|
821
|
+
@network = Resources::Network.new(client)
|
|
822
|
+
@subusers = Resources::Subusers.new(client)
|
|
823
|
+
@git = Resources::Git.new(client)
|
|
824
|
+
@account = Resources::Account.new(client)
|
|
825
|
+
@server_creation = Resources::ServerCreation.new(client)
|
|
826
|
+
@databases = Resources::Databases.new(client)
|
|
827
|
+
@billing = Resources::Billing.new(client)
|
|
828
|
+
@monitoring = Resources::Monitoring.new(client)
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
# The live console: websocket, streamed, polled or relayed through the panel. Loaded on first
|
|
832
|
+
# use, so code that never opens a console does not load it.
|
|
833
|
+
def console
|
|
834
|
+
@console ||= begin
|
|
835
|
+
require_relative "console"
|
|
836
|
+
ConsoleApi.new(@client)
|
|
837
|
+
end
|
|
838
|
+
end
|
|
839
|
+
|
|
840
|
+
def inspect
|
|
841
|
+
"#<#{self.class.name}>"
|
|
842
|
+
end
|
|
843
|
+
end
|
|
844
|
+
end
|