2024-05-23 20:40:31 +00:00
|
|
|
import { reportError } from 'app/client/models/errors';
|
2024-04-30 00:31:10 +00:00
|
|
|
import { BootProbeIds, BootProbeInfo, BootProbeResult } from 'app/common/BootProbe';
|
2024-05-23 20:40:31 +00:00
|
|
|
import { InstallAPI } from 'app/common/InstallAPI';
|
2024-04-30 00:31:10 +00:00
|
|
|
import { getGristConfig } from 'app/common/urlUtils';
|
|
|
|
import { Disposable, Observable, UseCBOwner } from 'grainjs';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Manage a collection of checks about the status of Grist, for
|
|
|
|
* presentation on the admin panel or the boot page.
|
|
|
|
*/
|
|
|
|
export class AdminChecks {
|
|
|
|
|
|
|
|
// The back end will offer a set of probes (diagnostics) we
|
|
|
|
// can use. Probes have unique IDs.
|
|
|
|
public probes: Observable<BootProbeInfo[]>;
|
|
|
|
|
|
|
|
// Keep track of probe requests we are making, by probe ID.
|
|
|
|
private _requests: Map<string, AdminCheckRunner>;
|
|
|
|
|
|
|
|
// Keep track of probe results we have received, by probe ID.
|
|
|
|
private _results: Map<string, Observable<BootProbeResult>>;
|
|
|
|
|
2024-05-23 20:40:31 +00:00
|
|
|
constructor(private _parent: Disposable, private _installAPI: InstallAPI) {
|
2024-04-30 00:31:10 +00:00
|
|
|
this.probes = Observable.create(_parent, []);
|
|
|
|
this._results = new Map();
|
|
|
|
this._requests = new Map();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fetch a list of available checks from the server.
|
|
|
|
*/
|
|
|
|
public async fetchAvailableChecks() {
|
|
|
|
const config = getGristConfig();
|
|
|
|
const errMessage = config.errMessage;
|
|
|
|
if (!errMessage) {
|
2024-05-23 20:40:31 +00:00
|
|
|
const _probes = await this._installAPI.getChecks().catch(reportError);
|
|
|
|
if (!this._parent.isDisposed()) {
|
|
|
|
// Currently, probes are forbidden if not admin.
|
|
|
|
// TODO: May want to relax this to allow some probes that help
|
|
|
|
// diagnose some initial auth problems.
|
|
|
|
this.probes.set(_probes ? _probes.probes : []);
|
|
|
|
}
|
|
|
|
return _probes;
|
2024-04-30 00:31:10 +00:00
|
|
|
}
|
2024-05-23 20:40:31 +00:00
|
|
|
return [];
|
2024-04-30 00:31:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Request the result of one of the available checks. Returns information
|
|
|
|
* about the check and a way to observe the result when it arrives.
|
|
|
|
*/
|
|
|
|
public requestCheck(probe: BootProbeInfo): AdminCheckRequest {
|
|
|
|
const {id} = probe;
|
|
|
|
let result = this._results.get(id);
|
|
|
|
if (!result) {
|
2024-05-23 20:40:31 +00:00
|
|
|
result = Observable.create(this._parent, {status: 'none'});
|
2024-04-30 00:31:10 +00:00
|
|
|
this._results.set(id, result);
|
|
|
|
}
|
|
|
|
let request = this._requests.get(id);
|
|
|
|
if (!request) {
|
2024-05-23 20:40:31 +00:00
|
|
|
request = new AdminCheckRunner(this._installAPI, id, this._results, this._parent);
|
2024-04-30 00:31:10 +00:00
|
|
|
this._requests.set(id, request);
|
|
|
|
}
|
|
|
|
request.start();
|
|
|
|
return {
|
|
|
|
probe,
|
|
|
|
result,
|
|
|
|
details: probeDetails[id],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Request the result of a check, by its id.
|
|
|
|
*/
|
|
|
|
public requestCheckById(use: UseCBOwner, id: BootProbeIds): AdminCheckRequest|undefined {
|
|
|
|
const probe = use(this.probes).find(p => p.id === id);
|
|
|
|
if (!probe) { return; }
|
|
|
|
return this.requestCheck(probe);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Information about a check and a way to observe its result once available.
|
|
|
|
*/
|
|
|
|
export interface AdminCheckRequest {
|
|
|
|
probe: BootProbeInfo,
|
|
|
|
result: Observable<BootProbeResult>,
|
|
|
|
details: ProbeDetails,
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Manage a single check.
|
|
|
|
*/
|
|
|
|
export class AdminCheckRunner {
|
2024-05-23 20:40:31 +00:00
|
|
|
constructor(private _installAPI: InstallAPI,
|
|
|
|
public id: string,
|
|
|
|
public results: Map<string, Observable<BootProbeResult>>,
|
2024-04-30 00:31:10 +00:00
|
|
|
public parent: Disposable) {
|
2024-05-23 20:40:31 +00:00
|
|
|
this._installAPI.runCheck(id).then(result => {
|
|
|
|
if (parent.isDisposed()) { return; }
|
2024-04-30 00:31:10 +00:00
|
|
|
const ob = results.get(id);
|
|
|
|
if (ob) {
|
2024-05-23 20:40:31 +00:00
|
|
|
ob.set(result);
|
2024-04-30 00:31:10 +00:00
|
|
|
}
|
|
|
|
}).catch(e => console.error(e));
|
|
|
|
}
|
|
|
|
|
|
|
|
public start() {
|
|
|
|
let result = this.results.get(this.id);
|
|
|
|
if (!result) {
|
2024-05-23 20:40:31 +00:00
|
|
|
result = Observable.create(this.parent, {status: 'none'});
|
2024-04-30 00:31:10 +00:00
|
|
|
this.results.set(this.id, result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Basic information about diagnostics is kept on the server,
|
|
|
|
* but it can be useful to show extra details and tips in the
|
|
|
|
* client.
|
|
|
|
*/
|
2024-05-23 20:40:31 +00:00
|
|
|
export const probeDetails: Record<string, ProbeDetails> = {
|
2024-04-30 00:31:10 +00:00
|
|
|
'boot-page': {
|
|
|
|
info: `
|
|
|
|
This boot page should not be too easy to access. Either turn
|
|
|
|
it off when configuration is ok (by unsetting GRIST_BOOT_KEY)
|
|
|
|
or make GRIST_BOOT_KEY long and cryptographically secure.
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
|
|
|
|
'health-check': {
|
|
|
|
info: `
|
|
|
|
Grist has a small built-in health check often used when running
|
|
|
|
it as a container.
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
|
|
|
|
'host-header': {
|
|
|
|
info: `
|
|
|
|
Requests arriving to Grist should have an accurate Host
|
|
|
|
header. This is essential when GRIST_SERVE_SAME_ORIGIN
|
|
|
|
is set.
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
|
2024-05-23 20:40:31 +00:00
|
|
|
'sandboxing': {
|
|
|
|
info: `
|
|
|
|
Grist allows for very powerful formulas, using Python.
|
|
|
|
We recommend setting the environment variable
|
|
|
|
GRIST_SANDBOX_FLAVOR to gvisor if your hardware
|
|
|
|
supports it (most will), to run formulas in each document
|
|
|
|
within a sandbox isolated from other documents and isolated
|
|
|
|
from the network.
|
|
|
|
`
|
|
|
|
},
|
|
|
|
|
2024-04-30 00:31:10 +00:00
|
|
|
'system-user': {
|
|
|
|
info: `
|
|
|
|
It is good practice not to run Grist as the root user.
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
|
|
|
|
'reachable': {
|
|
|
|
info: `
|
|
|
|
The main page of Grist should be available.
|
2024-05-23 20:40:31 +00:00
|
|
|
`
|
|
|
|
},
|
|
|
|
|
|
|
|
'websockets': {
|
|
|
|
// TODO: add a link to https://support.getgrist.com/self-managed/#how-do-i-run-grist-on-a-server
|
|
|
|
info: `
|
|
|
|
Websocket connections need HTTP 1.1 and the ability to pass a few
|
|
|
|
extra headers in order to work. Sometimes a reverse proxy can
|
|
|
|
interfere with these requirements.
|
2024-04-30 00:31:10 +00:00
|
|
|
`
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Information about the probe.
|
|
|
|
*/
|
|
|
|
export interface ProbeDetails {
|
|
|
|
info: string;
|
|
|
|
}
|