mirror of https://github.com/coder/coder.git
feat(coderd): store workspace proxy version in the database (#10790)
Stores workspace proxy version in database upon registration.
This commit is contained in:
parent
7060069034
commit
abafc0863c
|
@ -254,7 +254,7 @@ func TestIsDERPPath(t *testing.T) {
|
|||
//{
|
||||
// path: "/derp",
|
||||
// expected: true,
|
||||
//},
|
||||
// },
|
||||
{
|
||||
path: "/derp/",
|
||||
expected: true,
|
||||
|
|
|
@ -11898,6 +11898,9 @@ const docTemplate = `{
|
|||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
},
|
||||
"wildcard_hostname": {
|
||||
"description": "WildcardHostname is the wildcard hostname for subdomain apps.\nE.g. *.us.example.com\nE.g. *--suffix.au.example.com\nOptional. Does not need to be on the same domain as PathAppURL.",
|
||||
"type": "string"
|
||||
|
|
|
@ -10827,6 +10827,9 @@
|
|||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
},
|
||||
"wildcard_hostname": {
|
||||
"description": "WildcardHostname is the wildcard hostname for subdomain apps.\nE.g. *.us.example.com\nE.g. *--suffix.au.example.com\nOptional. Does not need to be on the same domain as PathAppURL.",
|
||||
"type": "string"
|
||||
|
|
|
@ -5563,6 +5563,7 @@ func (q *FakeQuerier) RegisterWorkspaceProxy(_ context.Context, arg database.Reg
|
|||
p.WildcardHostname = arg.WildcardHostname
|
||||
p.DerpEnabled = arg.DerpEnabled
|
||||
p.DerpOnly = arg.DerpOnly
|
||||
p.Version = arg.Version
|
||||
p.UpdatedAt = dbtime.Now()
|
||||
q.workspaceProxies[i] = p
|
||||
return p, nil
|
||||
|
|
|
@ -1127,7 +1127,8 @@ CREATE TABLE workspace_proxies (
|
|||
token_hashed_secret bytea NOT NULL,
|
||||
region_id integer NOT NULL,
|
||||
derp_enabled boolean DEFAULT true NOT NULL,
|
||||
derp_only boolean DEFAULT false NOT NULL
|
||||
derp_only boolean DEFAULT false NOT NULL,
|
||||
version text DEFAULT ''::text NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN workspace_proxies.icon IS 'Expects an emoji character. (/emojis/1f1fa-1f1f8.png)';
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
BEGIN;
|
||||
ALTER TABLE workspace_proxies DROP COLUMN version;
|
||||
COMMIT;
|
|
@ -0,0 +1,3 @@
|
|||
BEGIN;
|
||||
ALTER TABLE workspace_proxies ADD COLUMN version TEXT DEFAULT ''::TEXT NOT NULL;
|
||||
COMMIT;
|
|
@ -2364,7 +2364,8 @@ type WorkspaceProxy struct {
|
|||
RegionID int32 `db:"region_id" json:"region_id"`
|
||||
DerpEnabled bool `db:"derp_enabled" json:"derp_enabled"`
|
||||
// Disables app/terminal proxying for this proxy and only acts as a DERP relay.
|
||||
DerpOnly bool `db:"derp_only" json:"derp_only"`
|
||||
DerpOnly bool `db:"derp_only" json:"derp_only"`
|
||||
Version string `db:"version" json:"version"`
|
||||
}
|
||||
|
||||
type WorkspaceResource struct {
|
||||
|
|
|
@ -3667,7 +3667,7 @@ func (q *sqlQuerier) UpdateProvisionerJobWithCompleteByID(ctx context.Context, a
|
|||
|
||||
const getWorkspaceProxies = `-- name: GetWorkspaceProxies :many
|
||||
SELECT
|
||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only
|
||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only, version
|
||||
FROM
|
||||
workspace_proxies
|
||||
WHERE
|
||||
|
@ -3697,6 +3697,7 @@ func (q *sqlQuerier) GetWorkspaceProxies(ctx context.Context) ([]WorkspaceProxy,
|
|||
&i.RegionID,
|
||||
&i.DerpEnabled,
|
||||
&i.DerpOnly,
|
||||
&i.Version,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -3713,7 +3714,7 @@ func (q *sqlQuerier) GetWorkspaceProxies(ctx context.Context) ([]WorkspaceProxy,
|
|||
|
||||
const getWorkspaceProxyByHostname = `-- name: GetWorkspaceProxyByHostname :one
|
||||
SELECT
|
||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only
|
||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only, version
|
||||
FROM
|
||||
workspace_proxies
|
||||
WHERE
|
||||
|
@ -3772,13 +3773,14 @@ func (q *sqlQuerier) GetWorkspaceProxyByHostname(ctx context.Context, arg GetWor
|
|||
&i.RegionID,
|
||||
&i.DerpEnabled,
|
||||
&i.DerpOnly,
|
||||
&i.Version,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWorkspaceProxyByID = `-- name: GetWorkspaceProxyByID :one
|
||||
SELECT
|
||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only
|
||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only, version
|
||||
FROM
|
||||
workspace_proxies
|
||||
WHERE
|
||||
|
@ -3804,13 +3806,14 @@ func (q *sqlQuerier) GetWorkspaceProxyByID(ctx context.Context, id uuid.UUID) (W
|
|||
&i.RegionID,
|
||||
&i.DerpEnabled,
|
||||
&i.DerpOnly,
|
||||
&i.Version,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWorkspaceProxyByName = `-- name: GetWorkspaceProxyByName :one
|
||||
SELECT
|
||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only
|
||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only, version
|
||||
FROM
|
||||
workspace_proxies
|
||||
WHERE
|
||||
|
@ -3837,6 +3840,7 @@ func (q *sqlQuerier) GetWorkspaceProxyByName(ctx context.Context, name string) (
|
|||
&i.RegionID,
|
||||
&i.DerpEnabled,
|
||||
&i.DerpOnly,
|
||||
&i.Version,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
@ -3858,7 +3862,7 @@ INSERT INTO
|
|||
deleted
|
||||
)
|
||||
VALUES
|
||||
($1, '', '', $2, $3, $4, $5, $6, $7, $8, $9, false) RETURNING id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only
|
||||
($1, '', '', $2, $3, $4, $5, $6, $7, $8, $9, false) RETURNING id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only, version
|
||||
`
|
||||
|
||||
type InsertWorkspaceProxyParams struct {
|
||||
|
@ -3900,6 +3904,7 @@ func (q *sqlQuerier) InsertWorkspaceProxy(ctx context.Context, arg InsertWorkspa
|
|||
&i.RegionID,
|
||||
&i.DerpEnabled,
|
||||
&i.DerpOnly,
|
||||
&i.Version,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
@ -3912,10 +3917,11 @@ SET
|
|||
wildcard_hostname = $2 :: text,
|
||||
derp_enabled = $3 :: boolean,
|
||||
derp_only = $4 :: boolean,
|
||||
version = $5 :: text,
|
||||
updated_at = Now()
|
||||
WHERE
|
||||
id = $5
|
||||
RETURNING id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only
|
||||
id = $6
|
||||
RETURNING id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only, version
|
||||
`
|
||||
|
||||
type RegisterWorkspaceProxyParams struct {
|
||||
|
@ -3923,6 +3929,7 @@ type RegisterWorkspaceProxyParams struct {
|
|||
WildcardHostname string `db:"wildcard_hostname" json:"wildcard_hostname"`
|
||||
DerpEnabled bool `db:"derp_enabled" json:"derp_enabled"`
|
||||
DerpOnly bool `db:"derp_only" json:"derp_only"`
|
||||
Version string `db:"version" json:"version"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
}
|
||||
|
||||
|
@ -3932,6 +3939,7 @@ func (q *sqlQuerier) RegisterWorkspaceProxy(ctx context.Context, arg RegisterWor
|
|||
arg.WildcardHostname,
|
||||
arg.DerpEnabled,
|
||||
arg.DerpOnly,
|
||||
arg.Version,
|
||||
arg.ID,
|
||||
)
|
||||
var i WorkspaceProxy
|
||||
|
@ -3949,6 +3957,7 @@ func (q *sqlQuerier) RegisterWorkspaceProxy(ctx context.Context, arg RegisterWor
|
|||
&i.RegionID,
|
||||
&i.DerpEnabled,
|
||||
&i.DerpOnly,
|
||||
&i.Version,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
@ -3971,7 +3980,7 @@ SET
|
|||
updated_at = Now()
|
||||
WHERE
|
||||
id = $5
|
||||
RETURNING id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only
|
||||
RETURNING id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret, region_id, derp_enabled, derp_only, version
|
||||
`
|
||||
|
||||
type UpdateWorkspaceProxyParams struct {
|
||||
|
@ -4006,6 +4015,7 @@ func (q *sqlQuerier) UpdateWorkspaceProxy(ctx context.Context, arg UpdateWorkspa
|
|||
&i.RegionID,
|
||||
&i.DerpEnabled,
|
||||
&i.DerpOnly,
|
||||
&i.Version,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ SET
|
|||
wildcard_hostname = @wildcard_hostname :: text,
|
||||
derp_enabled = @derp_enabled :: boolean,
|
||||
derp_only = @derp_only :: boolean,
|
||||
version = @version :: text,
|
||||
updated_at = Now()
|
||||
WHERE
|
||||
id = @id
|
||||
|
|
|
@ -59,6 +59,7 @@ type WorkspaceProxy struct {
|
|||
CreatedAt time.Time `json:"created_at" format:"date-time" table:"created_at,default_sort"`
|
||||
UpdatedAt time.Time `json:"updated_at" format:"date-time" table:"updated_at"`
|
||||
Deleted bool `json:"deleted" table:"deleted"`
|
||||
Version string `json:"version" table:"version"`
|
||||
}
|
||||
|
||||
type CreateWorkspaceProxyRequest struct {
|
||||
|
|
|
@ -20,7 +20,7 @@ We track the following resources:
|
|||
| User<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>avatar_url</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>true</td></tr><tr><td>email</td><td>true</td></tr><tr><td>hashed_password</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_seen_at</td><td>false</td></tr><tr><td>login_type</td><td>true</td></tr><tr><td>quiet_hours_schedule</td><td>true</td></tr><tr><td>rbac_roles</td><td>true</td></tr><tr><td>status</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>username</td><td>true</td></tr></tbody></table> |
|
||||
| Workspace<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>automatic_updates</td><td>true</td></tr><tr><td>autostart_schedule</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>deleting_at</td><td>true</td></tr><tr><td>dormant_at</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_used_at</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>owner_id</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>ttl</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
|
||||
| WorkspaceBuild<br><i>start, stop</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>build_number</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>daily_cost</td><td>false</td></tr><tr><td>deadline</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>initiator_by_avatar_url</td><td>false</td></tr><tr><td>initiator_by_username</td><td>false</td></tr><tr><td>initiator_id</td><td>false</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>max_deadline</td><td>false</td></tr><tr><td>provisioner_state</td><td>false</td></tr><tr><td>reason</td><td>false</td></tr><tr><td>template_version_id</td><td>true</td></tr><tr><td>transition</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>workspace_id</td><td>false</td></tr></tbody></table> |
|
||||
| WorkspaceProxy<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>derp_enabled</td><td>true</td></tr><tr><td>derp_only</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>region_id</td><td>true</td></tr><tr><td>token_hashed_secret</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>url</td><td>true</td></tr><tr><td>wildcard_hostname</td><td>true</td></tr></tbody></table> |
|
||||
| WorkspaceProxy<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>derp_enabled</td><td>true</td></tr><tr><td>derp_only</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>region_id</td><td>true</td></tr><tr><td>token_hashed_secret</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>url</td><td>true</td></tr><tr><td>version</td><td>true</td></tr><tr><td>wildcard_hostname</td><td>true</td></tr></tbody></table> |
|
||||
|
||||
<!-- End generated by 'make docs/admin/audit-logs.md'. -->
|
||||
|
||||
|
|
|
@ -1513,6 +1513,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaceproxies \
|
|||
"status": "ok"
|
||||
},
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"version": "string",
|
||||
"wildcard_hostname": "string"
|
||||
}
|
||||
]
|
||||
|
@ -1551,6 +1552,7 @@ Status Code **200**
|
|||
| `»»»» warnings` | array | false | | Warnings do not prevent the workspace proxy from being healthy, but should be addressed. |
|
||||
| `»»» status` | [codersdk.ProxyHealthStatus](schemas.md#codersdkproxyhealthstatus) | false | | |
|
||||
| `»» updated_at` | string(date-time) | false | | |
|
||||
| `»» version` | string | false | | |
|
||||
| `»» wildcard_hostname` | string | false | | Wildcard hostname is the wildcard hostname for subdomain apps. E.g. _.us.example.com E.g. _--suffix.au.example.com Optional. Does not need to be on the same domain as PathAppURL. |
|
||||
|
||||
#### Enumerated Values
|
||||
|
@ -1619,6 +1621,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaceproxies \
|
|||
"status": "ok"
|
||||
},
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"version": "string",
|
||||
"wildcard_hostname": "string"
|
||||
}
|
||||
```
|
||||
|
@ -1675,6 +1678,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaceproxies/{workspaceproxy} \
|
|||
"status": "ok"
|
||||
},
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"version": "string",
|
||||
"wildcard_hostname": "string"
|
||||
}
|
||||
```
|
||||
|
@ -1789,6 +1793,7 @@ curl -X PATCH http://coder-server:8080/api/v2/workspaceproxies/{workspaceproxy}
|
|||
"status": "ok"
|
||||
},
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"version": "string",
|
||||
"wildcard_hostname": "string"
|
||||
}
|
||||
```
|
||||
|
|
|
@ -4086,6 +4086,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
|||
"status": "ok"
|
||||
},
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"version": "string",
|
||||
"wildcard_hostname": "string"
|
||||
}
|
||||
]
|
||||
|
@ -6649,6 +6650,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
"status": "ok"
|
||||
},
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"version": "string",
|
||||
"wildcard_hostname": "string"
|
||||
}
|
||||
```
|
||||
|
@ -6669,6 +6671,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
|||
| `path_app_url` | string | false | | Path app URL is the URL to the base path for path apps. Optional unless wildcard_hostname is set. E.g. https://us.example.com |
|
||||
| `status` | [codersdk.WorkspaceProxyStatus](#codersdkworkspaceproxystatus) | false | | Status is the latest status check of the proxy. This will be empty for deleted proxies. This value can be used to determine if a workspace proxy is healthy and ready to use. |
|
||||
| `updated_at` | string | false | | |
|
||||
| `version` | string | false | | |
|
||||
| `wildcard_hostname` | string | false | | Wildcard hostname is the wildcard hostname for subdomain apps. E.g. _.us.example.com E.g. _--suffix.au.example.com Optional. Does not need to be on the same domain as PathAppURL. |
|
||||
|
||||
## codersdk.WorkspaceProxyStatus
|
||||
|
|
|
@ -207,6 +207,7 @@ var auditableResourcesTypes = map[any]map[string]Action{
|
|||
"derp_enabled": ActionTrack,
|
||||
"derp_only": ActionTrack,
|
||||
"region_id": ActionTrack,
|
||||
"version": ActionTrack,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ func insertProxy(t *testing.T, db database.Store, url string) database.Workspace
|
|||
Url: url,
|
||||
WildcardHostname: "",
|
||||
ID: proxy.ID,
|
||||
Version: `v2.34.5-test+beefcake`,
|
||||
})
|
||||
require.NoError(t, err, "failed to update proxy")
|
||||
return proxy
|
||||
|
|
|
@ -625,6 +625,7 @@ func (api *API) workspaceProxyRegister(rw http.ResponseWriter, r *http.Request)
|
|||
DerpEnabled: req.DerpEnabled,
|
||||
DerpOnly: req.DerpOnly,
|
||||
WildcardHostname: req.WildcardHostname,
|
||||
Version: req.Version,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("register workspace proxy: %w", err)
|
||||
|
@ -951,6 +952,7 @@ func convertProxy(p database.WorkspaceProxy, status proxyhealth.ProxyStatus) cod
|
|||
CreatedAt: p.CreatedAt,
|
||||
UpdatedAt: p.UpdatedAt,
|
||||
Deleted: p.Deleted,
|
||||
Version: p.Version,
|
||||
Status: codersdk.WorkspaceProxyStatus{
|
||||
Status: codersdk.ProxyHealthStatus(status.Status),
|
||||
Report: status.Report,
|
||||
|
|
|
@ -1581,6 +1581,7 @@ export interface WorkspaceProxy extends Region {
|
|||
readonly created_at: string;
|
||||
readonly updated_at: string;
|
||||
readonly deleted: boolean;
|
||||
readonly version: string;
|
||||
}
|
||||
|
||||
// From codersdk/deployment.go
|
||||
|
|
|
@ -85,6 +85,7 @@ export const MockPrimaryWorkspaceProxy: TypesGen.WorkspaceProxy = {
|
|||
derp_only: false,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
version: "v2.34.5-test+primary",
|
||||
deleted: false,
|
||||
status: {
|
||||
status: "ok",
|
||||
|
@ -105,6 +106,7 @@ export const MockHealthyWildWorkspaceProxy: TypesGen.WorkspaceProxy = {
|
|||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
deleted: false,
|
||||
version: "v2.34.5-test+haswildcard",
|
||||
status: {
|
||||
status: "ok",
|
||||
checked_at: new Date().toISOString(),
|
||||
|
@ -123,6 +125,7 @@ export const MockUnhealthyWildWorkspaceProxy: TypesGen.WorkspaceProxy = {
|
|||
derp_only: true,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
version: "v2.34.5-test+unhealthy",
|
||||
deleted: false,
|
||||
status: {
|
||||
status: "unhealthy",
|
||||
|
@ -151,6 +154,7 @@ export const MockWorkspaceProxies: TypesGen.WorkspaceProxy[] = [
|
|||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
deleted: false,
|
||||
version: "v2.34.5-test+nowildcard",
|
||||
status: {
|
||||
status: "ok",
|
||||
checked_at: new Date().toISOString(),
|
||||
|
|
Loading…
Reference in New Issue