@ninedeploy/schemas 0.16.7 → 0.16.9
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/dist/canary.d.ts +66 -0
- package/dist/canary.d.ts.map +1 -0
- package/dist/canary.js +61 -0
- package/dist/canary.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/service.d.ts +14 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +5 -0
- package/dist/service.js.map +1 -1
- package/dist/uptime.d.ts +204 -0
- package/dist/uptime.d.ts.map +1 -0
- package/dist/uptime.js +180 -0
- package/dist/uptime.js.map +1 -0
- package/package.json +1 -1
- package/src/canary.ts +95 -0
- package/src/index.ts +2 -0
- package/src/service.ts +5 -0
- package/src/uptime.ts +273 -0
package/dist/uptime.d.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* 0.16.8 uptime monitors and public status pages.
|
|
4
|
+
*
|
|
5
|
+
* A monitor probes a target from the panel host on an interval: an HTTP(S)
|
|
6
|
+
* URL (status code, optional keyword in the body) or a TCP `host:port`. Its
|
|
7
|
+
* state goes up / down after `failuresBeforeDown` failed checks in a row, and
|
|
8
|
+
* every transition is an `alert.monitor_down` / `alert.monitor_up` event on
|
|
9
|
+
* the existing notification channels. A status page shows a chosen set of
|
|
10
|
+
* monitors publicly, by name only (never the target).
|
|
11
|
+
*/
|
|
12
|
+
export declare const MONITOR_KINDS: readonly ['http', 'tcp'];
|
|
13
|
+
export declare const monitorKind: z.ZodEnum<{
|
|
14
|
+
http: "http";
|
|
15
|
+
tcp: "tcp";
|
|
16
|
+
}>;
|
|
17
|
+
export type MonitorKind = z.infer<typeof monitorKind>;
|
|
18
|
+
export declare const MONITOR_METHODS: readonly ['GET', 'HEAD'];
|
|
19
|
+
export declare const monitorMethod: z.ZodEnum<{
|
|
20
|
+
GET: "GET";
|
|
21
|
+
HEAD: "HEAD";
|
|
22
|
+
}>;
|
|
23
|
+
export declare const MONITOR_STATUSES: readonly ['pending', 'up', 'down', 'paused'];
|
|
24
|
+
export type MonitorStatus = (typeof MONITOR_STATUSES)[number];
|
|
25
|
+
export declare const MONITOR_INTERVAL_MIN = 30;
|
|
26
|
+
export declare const MONITOR_INTERVAL_MAX = 3600;
|
|
27
|
+
export declare const MONITOR_TIMEOUT_MAX = 60;
|
|
28
|
+
export declare const MONITOR_FAILURES_MAX = 10;
|
|
29
|
+
export declare const MONITOR_KEYWORD_MAX = 200;
|
|
30
|
+
/** Status pages show at most this many monitors. */
|
|
31
|
+
export declare const STATUS_PAGE_MONITORS_MAX = 50;
|
|
32
|
+
/** Days of history a status page shows. */
|
|
33
|
+
export declare const STATUS_PAGE_DAYS = 90;
|
|
34
|
+
export declare const DEFAULT_EXPECTED_STATUS = "200-399";
|
|
35
|
+
/**
|
|
36
|
+
* The status codes an HTTP check accepts: comma-separated codes and ranges,
|
|
37
|
+
* each between 100 and 599 (`200-299`, `200,204,301-302`). Null when the
|
|
38
|
+
* text is not such a list.
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseExpectedStatus(spec: string): Array<[number, number]> | null;
|
|
41
|
+
/** Whether `code` is one of the codes `spec` accepts (an invalid spec accepts none). */
|
|
42
|
+
export declare function statusMatches(code: number, spec: string): boolean;
|
|
43
|
+
/** `host:port` for a TCP check: a hostname, an IPv4 address or a bracketed IPv6 address, and a port 1–65535. */
|
|
44
|
+
export declare function parseTcpTarget(target: string): {
|
|
45
|
+
host: string;
|
|
46
|
+
port: number;
|
|
47
|
+
} | null;
|
|
48
|
+
/** Why `target` is not valid for `kind`, or null. */
|
|
49
|
+
export declare function monitorTargetProblem(kind: MonitorKind, target: string): string | null;
|
|
50
|
+
/** `POST /v1/monitors` (operator). */
|
|
51
|
+
export declare const monitorCreate: z.ZodObject<{
|
|
52
|
+
name: z.ZodString;
|
|
53
|
+
kind: z.ZodEnum<{
|
|
54
|
+
http: "http";
|
|
55
|
+
tcp: "tcp";
|
|
56
|
+
}>;
|
|
57
|
+
target: z.ZodString;
|
|
58
|
+
serviceId: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
|
|
59
|
+
method: z.ZodDefault<z.ZodEnum<{
|
|
60
|
+
GET: "GET";
|
|
61
|
+
HEAD: "HEAD";
|
|
62
|
+
}>>;
|
|
63
|
+
expectedStatus: z.ZodDefault<z.ZodString>;
|
|
64
|
+
keyword: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
65
|
+
intervalSeconds: z.ZodDefault<z.ZodNumber>;
|
|
66
|
+
timeoutSeconds: z.ZodDefault<z.ZodNumber>;
|
|
67
|
+
failuresBeforeDown: z.ZodDefault<z.ZodNumber>;
|
|
68
|
+
followRedirects: z.ZodDefault<z.ZodBoolean>;
|
|
69
|
+
verifyTls: z.ZodDefault<z.ZodBoolean>;
|
|
70
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
71
|
+
}, z.core.$strict>;
|
|
72
|
+
export type MonitorCreateInput = z.input<typeof monitorCreate>;
|
|
73
|
+
export type MonitorCreate = z.infer<typeof monitorCreate>;
|
|
74
|
+
/**
|
|
75
|
+
* `PATCH /v1/monitors/:id` (operator). Partial; the route checks the merged
|
|
76
|
+
* kind, target, method and keyword with {@link monitorTargetProblem} again.
|
|
77
|
+
*/
|
|
78
|
+
export declare const monitorPatch: z.ZodObject<{
|
|
79
|
+
name: z.ZodOptional<z.ZodString>;
|
|
80
|
+
serviceId: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
81
|
+
kind: z.ZodOptional<z.ZodEnum<{
|
|
82
|
+
http: "http";
|
|
83
|
+
tcp: "tcp";
|
|
84
|
+
}>>;
|
|
85
|
+
target: z.ZodOptional<z.ZodString>;
|
|
86
|
+
method: z.ZodOptional<z.ZodEnum<{
|
|
87
|
+
GET: "GET";
|
|
88
|
+
HEAD: "HEAD";
|
|
89
|
+
}>>;
|
|
90
|
+
expectedStatus: z.ZodOptional<z.ZodString>;
|
|
91
|
+
keyword: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
92
|
+
intervalSeconds: z.ZodOptional<z.ZodNumber>;
|
|
93
|
+
timeoutSeconds: z.ZodOptional<z.ZodNumber>;
|
|
94
|
+
failuresBeforeDown: z.ZodOptional<z.ZodNumber>;
|
|
95
|
+
followRedirects: z.ZodOptional<z.ZodBoolean>;
|
|
96
|
+
verifyTls: z.ZodOptional<z.ZodBoolean>;
|
|
97
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
98
|
+
}, z.core.$strict>;
|
|
99
|
+
export type MonitorPatch = z.infer<typeof monitorPatch>;
|
|
100
|
+
/** A monitor as the API returns it. */
|
|
101
|
+
export interface MonitorView {
|
|
102
|
+
id: number;
|
|
103
|
+
name: string;
|
|
104
|
+
serviceId: number | null;
|
|
105
|
+
kind: MonitorKind;
|
|
106
|
+
target: string;
|
|
107
|
+
method: 'GET' | 'HEAD';
|
|
108
|
+
expectedStatus: string;
|
|
109
|
+
keyword: string | null;
|
|
110
|
+
intervalSeconds: number;
|
|
111
|
+
timeoutSeconds: number;
|
|
112
|
+
failuresBeforeDown: number;
|
|
113
|
+
followRedirects: boolean;
|
|
114
|
+
verifyTls: boolean;
|
|
115
|
+
enabled: boolean;
|
|
116
|
+
status: MonitorStatus;
|
|
117
|
+
/** When the status last changed. */
|
|
118
|
+
statusSince: string | null;
|
|
119
|
+
consecutiveFailures: number;
|
|
120
|
+
lastCheckedAt: string | null;
|
|
121
|
+
lastResponseMs: number | null;
|
|
122
|
+
lastStatusCode: number | null;
|
|
123
|
+
lastError: string | null;
|
|
124
|
+
/** https: when the certificate the last check saw expires. */
|
|
125
|
+
certExpiresAt: string | null;
|
|
126
|
+
/** Share of successful checks, 0–100 with two decimals; null without checks. */
|
|
127
|
+
uptime: {
|
|
128
|
+
h24: number | null;
|
|
129
|
+
d7: number | null;
|
|
130
|
+
d30: number | null;
|
|
131
|
+
};
|
|
132
|
+
createdAt: string;
|
|
133
|
+
}
|
|
134
|
+
/** One check result. */
|
|
135
|
+
export interface MonitorCheckView {
|
|
136
|
+
ts: string;
|
|
137
|
+
ok: boolean;
|
|
138
|
+
responseMs: number | null;
|
|
139
|
+
statusCode: number | null;
|
|
140
|
+
error: string | null;
|
|
141
|
+
}
|
|
142
|
+
/** A status page slug: lowercase letters, digits and dashes. */
|
|
143
|
+
export declare const statusPageSlug: z.ZodString;
|
|
144
|
+
/** `POST /v1/status-pages` (operator). */
|
|
145
|
+
export declare const statusPageCreate: z.ZodObject<{
|
|
146
|
+
slug: z.ZodString;
|
|
147
|
+
title: z.ZodString;
|
|
148
|
+
description: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
149
|
+
notice: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
150
|
+
published: z.ZodDefault<z.ZodBoolean>;
|
|
151
|
+
showResponseTimes: z.ZodDefault<z.ZodBoolean>;
|
|
152
|
+
monitorIds: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
|
|
153
|
+
}, z.core.$strict>;
|
|
154
|
+
export type StatusPageCreateInput = z.input<typeof statusPageCreate>;
|
|
155
|
+
/** `PATCH /v1/status-pages/:id` (operator). */
|
|
156
|
+
export declare const statusPagePatch: z.ZodObject<{
|
|
157
|
+
slug: z.ZodOptional<z.ZodString>;
|
|
158
|
+
title: z.ZodOptional<z.ZodString>;
|
|
159
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
160
|
+
notice: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
161
|
+
published: z.ZodOptional<z.ZodBoolean>;
|
|
162
|
+
showResponseTimes: z.ZodOptional<z.ZodBoolean>;
|
|
163
|
+
monitorIds: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
164
|
+
}, z.core.$strict>;
|
|
165
|
+
export type StatusPagePatch = z.infer<typeof statusPagePatch>;
|
|
166
|
+
export interface StatusPageView {
|
|
167
|
+
id: number;
|
|
168
|
+
slug: string;
|
|
169
|
+
title: string;
|
|
170
|
+
description: string | null;
|
|
171
|
+
notice: string | null;
|
|
172
|
+
published: boolean;
|
|
173
|
+
showResponseTimes: boolean;
|
|
174
|
+
monitorIds: number[];
|
|
175
|
+
/** Path of the public page on the panel. */
|
|
176
|
+
path: string;
|
|
177
|
+
createdAt: string;
|
|
178
|
+
}
|
|
179
|
+
/** One day of a monitor's history on a public status page. */
|
|
180
|
+
export interface PublicStatusDay {
|
|
181
|
+
/** `YYYY-MM-DD`, UTC. */
|
|
182
|
+
day: string;
|
|
183
|
+
/** Null: no checks that day. */
|
|
184
|
+
uptime: number | null;
|
|
185
|
+
}
|
|
186
|
+
/** `GET /v1/status/:slug`: what a public status page shows. Never a target. */
|
|
187
|
+
export interface PublicStatusPage {
|
|
188
|
+
title: string;
|
|
189
|
+
description: string | null;
|
|
190
|
+
notice: string | null;
|
|
191
|
+
/** `operational` when every monitor is up, `degraded` when some are down, `down` when all are. */
|
|
192
|
+
overall: 'operational' | 'degraded' | 'down' | 'unknown';
|
|
193
|
+
monitors: Array<{
|
|
194
|
+
name: string;
|
|
195
|
+
status: 'up' | 'down' | 'unknown';
|
|
196
|
+
uptime90d: number | null;
|
|
197
|
+
avgResponseMs: number | null;
|
|
198
|
+
days: PublicStatusDay[];
|
|
199
|
+
}>;
|
|
200
|
+
generatedAt: string;
|
|
201
|
+
}
|
|
202
|
+
/** The overall state of a status page from its monitors' states. */
|
|
203
|
+
export declare function overallStatus(states: Array<'up' | 'down' | 'unknown'>): PublicStatusPage['overall'];
|
|
204
|
+
//# sourceMappingURL=uptime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uptime.d.ts","sourceRoot":"","sources":["../src/uptime.ts"],"names":[],"mappings":"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"}
|
package/dist/uptime.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { id } from './common.js';
|
|
3
|
+
/**
|
|
4
|
+
* 0.16.8 uptime monitors and public status pages.
|
|
5
|
+
*
|
|
6
|
+
* A monitor probes a target from the panel host on an interval: an HTTP(S)
|
|
7
|
+
* URL (status code, optional keyword in the body) or a TCP `host:port`. Its
|
|
8
|
+
* state goes up / down after `failuresBeforeDown` failed checks in a row, and
|
|
9
|
+
* every transition is an `alert.monitor_down` / `alert.monitor_up` event on
|
|
10
|
+
* the existing notification channels. A status page shows a chosen set of
|
|
11
|
+
* monitors publicly, by name only (never the target).
|
|
12
|
+
*/
|
|
13
|
+
export const MONITOR_KINDS = ['http', 'tcp'];
|
|
14
|
+
export const monitorKind = z.enum(MONITOR_KINDS);
|
|
15
|
+
export const MONITOR_METHODS = ['GET', 'HEAD'];
|
|
16
|
+
export const monitorMethod = z.enum(MONITOR_METHODS);
|
|
17
|
+
export const MONITOR_STATUSES = ['pending', 'up', 'down', 'paused'];
|
|
18
|
+
export const MONITOR_INTERVAL_MIN = 30;
|
|
19
|
+
export const MONITOR_INTERVAL_MAX = 3600;
|
|
20
|
+
export const MONITOR_TIMEOUT_MAX = 60;
|
|
21
|
+
export const MONITOR_FAILURES_MAX = 10;
|
|
22
|
+
export const MONITOR_KEYWORD_MAX = 200;
|
|
23
|
+
/** Status pages show at most this many monitors. */
|
|
24
|
+
export const STATUS_PAGE_MONITORS_MAX = 50;
|
|
25
|
+
/** Days of history a status page shows. */
|
|
26
|
+
export const STATUS_PAGE_DAYS = 90;
|
|
27
|
+
export const DEFAULT_EXPECTED_STATUS = '200-399';
|
|
28
|
+
/**
|
|
29
|
+
* The status codes an HTTP check accepts: comma-separated codes and ranges,
|
|
30
|
+
* each between 100 and 599 (`200-299`, `200,204,301-302`). Null when the
|
|
31
|
+
* text is not such a list.
|
|
32
|
+
*/
|
|
33
|
+
export function parseExpectedStatus(spec) {
|
|
34
|
+
const parts = spec.split(',').map((p) => p.trim());
|
|
35
|
+
if (parts.length === 0 || parts.length > 20)
|
|
36
|
+
return null;
|
|
37
|
+
const ranges = [];
|
|
38
|
+
for (const part of parts) {
|
|
39
|
+
const m = /^(\d{3})(?:-(\d{3}))?$/.exec(part);
|
|
40
|
+
if (!m)
|
|
41
|
+
return null;
|
|
42
|
+
const lo = Number(m[1]);
|
|
43
|
+
const hi = m[2] === undefined ? lo : Number(m[2]);
|
|
44
|
+
if (lo < 100 || hi > 599 || lo > hi)
|
|
45
|
+
return null;
|
|
46
|
+
ranges.push([lo, hi]);
|
|
47
|
+
}
|
|
48
|
+
return ranges;
|
|
49
|
+
}
|
|
50
|
+
/** Whether `code` is one of the codes `spec` accepts (an invalid spec accepts none). */
|
|
51
|
+
export function statusMatches(code, spec) {
|
|
52
|
+
const ranges = parseExpectedStatus(spec);
|
|
53
|
+
return ranges !== null && ranges.some(([lo, hi]) => code >= lo && code <= hi);
|
|
54
|
+
}
|
|
55
|
+
/** `host:port` for a TCP check: a hostname, an IPv4 address or a bracketed IPv6 address, and a port 1–65535. */
|
|
56
|
+
export function parseTcpTarget(target) {
|
|
57
|
+
const m = /^(\[[0-9A-Fa-f:.]+\]|[A-Za-z0-9](?:[A-Za-z0-9.-]{0,251}[A-Za-z0-9])?):(\d{1,5})$/.exec(target);
|
|
58
|
+
if (!m)
|
|
59
|
+
return null;
|
|
60
|
+
const port = Number(m[2]);
|
|
61
|
+
if (port < 1 || port > 65535)
|
|
62
|
+
return null;
|
|
63
|
+
const host = m[1].startsWith('[') ? m[1].slice(1, -1) : m[1];
|
|
64
|
+
return { host, port };
|
|
65
|
+
}
|
|
66
|
+
/** Why `target` is not valid for `kind`, or null. */
|
|
67
|
+
export function monitorTargetProblem(kind, target) {
|
|
68
|
+
if (kind === 'tcp')
|
|
69
|
+
return parseTcpTarget(target) ? null : 'a TCP target is host:port (an IPv6 address in brackets)';
|
|
70
|
+
if (!httpUrl.safeParse(target).success)
|
|
71
|
+
return 'an HTTP target is an http:// or https:// URL';
|
|
72
|
+
// userinfo is everything between `//` and an `@` before the first `/`, `?` or `#`.
|
|
73
|
+
if (/^https?:\/\/[^/?#]*@/i.test(target))
|
|
74
|
+
return 'credentials in the URL are not allowed';
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const httpUrl = z.url({ protocol: /^https?$/ });
|
|
78
|
+
const monitorFields = {
|
|
79
|
+
name: z.string().trim().min(1).max(100),
|
|
80
|
+
/** The service the monitor belongs to (members who can see it see the monitor), or null for an operator-only monitor. */
|
|
81
|
+
serviceId: id.nullable(),
|
|
82
|
+
kind: monitorKind,
|
|
83
|
+
/** http: the URL; tcp: `host:port`. */
|
|
84
|
+
target: z.string().trim().min(1).max(2048),
|
|
85
|
+
method: monitorMethod,
|
|
86
|
+
/** http: accepted status codes, see {@link parseExpectedStatus}. */
|
|
87
|
+
expectedStatus: z
|
|
88
|
+
.string()
|
|
89
|
+
.trim()
|
|
90
|
+
.max(200)
|
|
91
|
+
.refine((v) => parseExpectedStatus(v) !== null, 'codes and ranges between 100 and 599, such as 200-299,301'),
|
|
92
|
+
/** http: text the body must contain (the first 1 MiB is read). */
|
|
93
|
+
keyword: z.string().max(MONITOR_KEYWORD_MAX).nullable(),
|
|
94
|
+
intervalSeconds: z.number().int().min(MONITOR_INTERVAL_MIN).max(MONITOR_INTERVAL_MAX),
|
|
95
|
+
timeoutSeconds: z.number().int().min(1).max(MONITOR_TIMEOUT_MAX),
|
|
96
|
+
failuresBeforeDown: z.number().int().min(1).max(MONITOR_FAILURES_MAX),
|
|
97
|
+
followRedirects: z.boolean(),
|
|
98
|
+
/** http: false accepts a self-signed or expired certificate. */
|
|
99
|
+
verifyTls: z.boolean(),
|
|
100
|
+
enabled: z.boolean(),
|
|
101
|
+
};
|
|
102
|
+
function checkTarget(v, ctx) {
|
|
103
|
+
if (v.kind !== undefined && v.target !== undefined) {
|
|
104
|
+
const problem = monitorTargetProblem(v.kind, v.target);
|
|
105
|
+
if (problem)
|
|
106
|
+
ctx.addIssue({ code: 'custom', path: ['target'], message: problem });
|
|
107
|
+
}
|
|
108
|
+
if (v.kind === 'tcp' && v.keyword)
|
|
109
|
+
ctx.addIssue({ code: 'custom', path: ['keyword'], message: 'a keyword applies to HTTP checks' });
|
|
110
|
+
if (v.method === 'HEAD' && v.keyword)
|
|
111
|
+
ctx.addIssue({ code: 'custom', path: ['keyword'], message: 'a HEAD request has no body to search; use GET' });
|
|
112
|
+
}
|
|
113
|
+
/** `POST /v1/monitors` (operator). */
|
|
114
|
+
export const monitorCreate = z
|
|
115
|
+
.object({
|
|
116
|
+
...monitorFields,
|
|
117
|
+
serviceId: monitorFields.serviceId.default(null),
|
|
118
|
+
method: monitorFields.method.default('GET'),
|
|
119
|
+
expectedStatus: monitorFields.expectedStatus.default(DEFAULT_EXPECTED_STATUS),
|
|
120
|
+
keyword: monitorFields.keyword.default(null),
|
|
121
|
+
intervalSeconds: monitorFields.intervalSeconds.default(60),
|
|
122
|
+
timeoutSeconds: monitorFields.timeoutSeconds.default(10),
|
|
123
|
+
failuresBeforeDown: monitorFields.failuresBeforeDown.default(2),
|
|
124
|
+
followRedirects: monitorFields.followRedirects.default(true),
|
|
125
|
+
verifyTls: monitorFields.verifyTls.default(true),
|
|
126
|
+
enabled: monitorFields.enabled.default(true),
|
|
127
|
+
})
|
|
128
|
+
.strict()
|
|
129
|
+
.superRefine(checkTarget);
|
|
130
|
+
/**
|
|
131
|
+
* `PATCH /v1/monitors/:id` (operator). Partial; the route checks the merged
|
|
132
|
+
* kind, target, method and keyword with {@link monitorTargetProblem} again.
|
|
133
|
+
*/
|
|
134
|
+
export const monitorPatch = z.object(monitorFields).partial().strict().superRefine(checkTarget);
|
|
135
|
+
/** A status page slug: lowercase letters, digits and dashes. */
|
|
136
|
+
export const statusPageSlug = z
|
|
137
|
+
.string()
|
|
138
|
+
.trim()
|
|
139
|
+
.min(1)
|
|
140
|
+
.max(64)
|
|
141
|
+
.regex(/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/, 'lowercase letters, digits and dashes, not starting or ending with a dash');
|
|
142
|
+
const statusPageFields = {
|
|
143
|
+
slug: statusPageSlug,
|
|
144
|
+
title: z.string().trim().min(1).max(100),
|
|
145
|
+
description: z.string().trim().max(500).nullable(),
|
|
146
|
+
/** A notice shown at the top (maintenance, an incident), or null. */
|
|
147
|
+
notice: z.string().trim().max(1000).nullable(),
|
|
148
|
+
/** Off: the page answers 404 to everyone. */
|
|
149
|
+
published: z.boolean(),
|
|
150
|
+
showResponseTimes: z.boolean(),
|
|
151
|
+
/** In display order, each once. */
|
|
152
|
+
monitorIds: z
|
|
153
|
+
.array(id)
|
|
154
|
+
.max(STATUS_PAGE_MONITORS_MAX)
|
|
155
|
+
.refine((ids) => new Set(ids).size === ids.length, 'each monitor once'),
|
|
156
|
+
};
|
|
157
|
+
/** `POST /v1/status-pages` (operator). */
|
|
158
|
+
export const statusPageCreate = z
|
|
159
|
+
.object({
|
|
160
|
+
...statusPageFields,
|
|
161
|
+
description: statusPageFields.description.default(null),
|
|
162
|
+
notice: statusPageFields.notice.default(null),
|
|
163
|
+
published: statusPageFields.published.default(false),
|
|
164
|
+
showResponseTimes: statusPageFields.showResponseTimes.default(true),
|
|
165
|
+
monitorIds: statusPageFields.monitorIds.default([]),
|
|
166
|
+
})
|
|
167
|
+
.strict();
|
|
168
|
+
/** `PATCH /v1/status-pages/:id` (operator). */
|
|
169
|
+
export const statusPagePatch = z.object(statusPageFields).partial().strict();
|
|
170
|
+
/** The overall state of a status page from its monitors' states. */
|
|
171
|
+
export function overallStatus(states) {
|
|
172
|
+
const known = states.filter((s) => s !== 'unknown');
|
|
173
|
+
if (known.length === 0)
|
|
174
|
+
return 'unknown';
|
|
175
|
+
const down = known.filter((s) => s === 'down').length;
|
|
176
|
+
if (down === 0)
|
|
177
|
+
return 'operational';
|
|
178
|
+
return down === known.length ? 'down' : 'degraded';
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=uptime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uptime.js","sourceRoot":"","sources":["../src/uptime.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/canary.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 0.16.9 canary releases.
|
|
5
|
+
*
|
|
6
|
+
* A docker service with a canary configuration does not switch all of its
|
|
7
|
+
* traffic to a new release at once. After the new container passes its
|
|
8
|
+
* healthcheck, Traefik sends it `steps[0]` percent of the requests, then each
|
|
9
|
+
* following step, for `stepSeconds` each, and the deploy finishes at 100 %.
|
|
10
|
+
* Each step is judged on the new release's healthcheck and, when traffic
|
|
11
|
+
* analytics is on, on its share of 5xx responses and its p95 latency. A step
|
|
12
|
+
* that fails sends every request back to the previous release and fails the
|
|
13
|
+
* deploy, which leaves the previous release serving, as any failed deploy does.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const CANARY_STEPS_MAX = 5;
|
|
17
|
+
export const CANARY_STEP_SECONDS_MIN = 30;
|
|
18
|
+
export const CANARY_STEP_SECONDS_MAX = 3600;
|
|
19
|
+
export const CANARY_DEFAULT_STEPS = [10, 50] as const;
|
|
20
|
+
export const CANARY_DEFAULT_STEP_SECONDS = 120;
|
|
21
|
+
export const CANARY_DEFAULT_MAX_ERROR_RATE = 5;
|
|
22
|
+
export const CANARY_DEFAULT_MIN_REQUESTS = 20;
|
|
23
|
+
|
|
24
|
+
export const canaryConfig = z
|
|
25
|
+
.object({
|
|
26
|
+
/** Percent of the requests the new release gets at each step, ascending, 1–99. */
|
|
27
|
+
steps: z
|
|
28
|
+
.array(z.number().int().min(1).max(99))
|
|
29
|
+
.min(1)
|
|
30
|
+
.max(CANARY_STEPS_MAX)
|
|
31
|
+
.refine((s) => s.every((v, i) => i === 0 || v > s[i - 1]!), 'Canary steps must go up, for example 10, 50'),
|
|
32
|
+
/** How long each step runs before it is judged. */
|
|
33
|
+
stepSeconds: z.number().int().min(CANARY_STEP_SECONDS_MIN).max(CANARY_STEP_SECONDS_MAX).default(CANARY_DEFAULT_STEP_SECONDS),
|
|
34
|
+
/** The highest share of 5xx responses, in percent, the new release may answer with. */
|
|
35
|
+
maxErrorRate: z.number().min(0).max(100).default(CANARY_DEFAULT_MAX_ERROR_RATE),
|
|
36
|
+
/** The highest p95 response time, in ms, the new release may have; null = not judged. */
|
|
37
|
+
maxP95Ms: z.number().int().min(10).max(600_000).nullable().default(null),
|
|
38
|
+
/** Fewer requests than this in a step are too few to judge the error rate and latency. */
|
|
39
|
+
minRequests: z.number().int().min(1).max(1_000_000).default(CANARY_DEFAULT_MIN_REQUESTS),
|
|
40
|
+
})
|
|
41
|
+
.strict();
|
|
42
|
+
export type CanaryConfig = z.infer<typeof canaryConfig>;
|
|
43
|
+
export type CanaryConfigInput = z.input<typeof canaryConfig>;
|
|
44
|
+
|
|
45
|
+
/** What one step measured on one release. */
|
|
46
|
+
export interface CanaryReleaseStats {
|
|
47
|
+
requests: number;
|
|
48
|
+
/** Responses with a 5xx status. */
|
|
49
|
+
errors: number;
|
|
50
|
+
/** Percent of the requests that were errors; null with no requests. */
|
|
51
|
+
errorRate: number | null;
|
|
52
|
+
/** p95 response time in ms; null with no requests. */
|
|
53
|
+
p95Ms: number | null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** `GET /v1/services/:id/canary`: the canary of a deploy in progress, or `active: false`. */
|
|
57
|
+
export interface CanaryStatusView {
|
|
58
|
+
active: boolean;
|
|
59
|
+
deploymentId?: number;
|
|
60
|
+
/** 1-based step running now, and how many there are. */
|
|
61
|
+
step?: number;
|
|
62
|
+
steps?: number;
|
|
63
|
+
/** Percent of the requests the new release gets now. */
|
|
64
|
+
weight?: number;
|
|
65
|
+
stepStartedAt?: string;
|
|
66
|
+
stepEndsAt?: string;
|
|
67
|
+
/** False when traffic analytics is off: steps are judged on the healthcheck only. */
|
|
68
|
+
measured?: boolean;
|
|
69
|
+
canary?: CanaryReleaseStats;
|
|
70
|
+
stable?: CanaryReleaseStats;
|
|
71
|
+
/** A promote was asked for and is being applied. */
|
|
72
|
+
promoting?: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Percent of `errors` in `requests`, rounded to two decimals; null with no requests. */
|
|
76
|
+
export function errorRatePercent(errors: number, requests: number): number | null {
|
|
77
|
+
if (requests <= 0) return null;
|
|
78
|
+
return Math.round((errors / requests) * 10_000) / 100;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Why a step's numbers fail the configuration, or null when they pass. Too
|
|
83
|
+
* few requests pass: there is not enough traffic to judge, and the step is
|
|
84
|
+
* left to the healthcheck.
|
|
85
|
+
*/
|
|
86
|
+
export function canaryStepProblem(stats: CanaryReleaseStats, cfg: Pick<CanaryConfig, 'maxErrorRate' | 'maxP95Ms' | 'minRequests'>): string | null {
|
|
87
|
+
if (stats.requests < cfg.minRequests) return null;
|
|
88
|
+
if (stats.errorRate != null && stats.errorRate > cfg.maxErrorRate) {
|
|
89
|
+
return `${stats.errorRate}% of ${stats.requests} requests failed with a 5xx (the limit is ${cfg.maxErrorRate}%)`;
|
|
90
|
+
}
|
|
91
|
+
if (cfg.maxP95Ms != null && stats.p95Ms != null && stats.p95Ms > cfg.maxP95Ms) {
|
|
92
|
+
return `p95 response time ${stats.p95Ms} ms over ${stats.requests} requests (the limit is ${cfg.maxP95Ms} ms)`;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
package/src/index.ts
CHANGED
package/src/service.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { canaryConfig } from './canary.js';
|
|
2
3
|
import { dockerLabels } from './dockerLabels.js';
|
|
3
4
|
import { containerPath, dockerVolumeName, envVarName, gitBranch, gitRepoUrl, httpPath, providerBaseUrl, repoBaseDir, repoRelativePath, slug } from './common.js';
|
|
4
5
|
|
|
@@ -392,6 +393,8 @@ export const updateService = z.object({
|
|
|
392
393
|
gitLfs: z.boolean().optional(),
|
|
393
394
|
/** Operator only: advanced container options; null clears them all. */
|
|
394
395
|
runtimeOptions: runtimeOptions.nullable().optional(),
|
|
396
|
+
/** 0.16.9: release new versions as a canary (docker services on the panel host); null turns it off. */
|
|
397
|
+
canary: canaryConfig.nullable().optional(),
|
|
395
398
|
port: z.number().int().min(1).max(65535).optional(),
|
|
396
399
|
publishedPort: z.number().int().min(1).max(65535).nullable().optional(),
|
|
397
400
|
/** Extra host ports, TCP or UDP, single or ranges (docker services on the panel host); null or [] clears them. */
|
|
@@ -485,6 +488,8 @@ export const service = z.object({
|
|
|
485
488
|
gitLfs: z.boolean().optional(),
|
|
486
489
|
/** Advanced container options (0.16.1); null = none. */
|
|
487
490
|
runtimeOptions: runtimeOptions.nullable().optional(),
|
|
491
|
+
/** Canary releases (0.16.9); null = every deploy switches all traffic at once. */
|
|
492
|
+
canary: canaryConfig.nullable().optional(),
|
|
488
493
|
autoUrl: z.string().nullable(),
|
|
489
494
|
port: z.number().int().nullable(),
|
|
490
495
|
publishedPort: z.number().int().nullable().optional(),
|