@opentermsarchive/engine 5.3.1 → 5.4.1
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/package.json +1 -1
- package/src/archivist/collection/index.js +75 -0
- package/src/archivist/collection/index.test.js +155 -0
- package/src/archivist/fetcher/htmlOnlyFetcher.js +4 -0
- package/src/collection-api/routes/index.js +3 -5
- package/src/collection-api/routes/metadata.js +6 -15
- package/src/collection-api/routes/metadata.test.js +1 -3
- package/src/index.js +4 -6
- package/src/logger/index.js +84 -3
package/package.json
CHANGED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
import config from 'config';
|
|
5
|
+
import yaml from 'js-yaml';
|
|
6
|
+
|
|
7
|
+
export const METADATA_FILE = 'metadata.yml';
|
|
8
|
+
export const INVENTORY_FILE = 'deployment/inventory.yml';
|
|
9
|
+
|
|
10
|
+
class Collection {
|
|
11
|
+
metadata;
|
|
12
|
+
id;
|
|
13
|
+
name;
|
|
14
|
+
inventory;
|
|
15
|
+
host;
|
|
16
|
+
hostConfig;
|
|
17
|
+
collectionPath;
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
this.collectionPath = path.resolve(process.cwd(), config.get('@opentermsarchive/engine.collectionPath'));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async loadYamlFile(filename) {
|
|
24
|
+
const content = await fs.readFile(path.join(this.collectionPath, filename), 'utf8');
|
|
25
|
+
|
|
26
|
+
return yaml.load(content);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async initialize() {
|
|
30
|
+
const fileLoadingPromises = await Promise.allSettled([
|
|
31
|
+
this.loadYamlFile(METADATA_FILE),
|
|
32
|
+
this.loadYamlFile(INVENTORY_FILE),
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const [ metadata, inventory ] = fileLoadingPromises.map(({ status, value, reason }) => {
|
|
36
|
+
if (status === 'fulfilled') {
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (reason && reason.code !== 'ENOENT') { // Ignore missing files errors (inventory and metadata are optional), but throw other errors like invalid YAML
|
|
41
|
+
throw reason;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return null;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (metadata) {
|
|
48
|
+
this.metadata = metadata;
|
|
49
|
+
this.id = metadata.id;
|
|
50
|
+
this.name = metadata.name;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (inventory) {
|
|
54
|
+
const [host] = Object.keys(inventory.all.hosts);
|
|
55
|
+
|
|
56
|
+
this.inventory = inventory;
|
|
57
|
+
this.host = host;
|
|
58
|
+
this.hostConfig = inventory.all.hosts[host];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let instancePromise = null;
|
|
66
|
+
|
|
67
|
+
export async function getCollection() {
|
|
68
|
+
if (!instancePromise) {
|
|
69
|
+
const collection = new Collection();
|
|
70
|
+
|
|
71
|
+
instancePromise = await collection.initialize();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return instancePromise;
|
|
75
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
import { expect } from 'chai';
|
|
5
|
+
import config from 'config';
|
|
6
|
+
|
|
7
|
+
import { METADATA_FILE, INVENTORY_FILE } from './index.js';
|
|
8
|
+
|
|
9
|
+
describe('Collection', () => {
|
|
10
|
+
const testCollectionPath = path.resolve(process.cwd(), config.get('@opentermsarchive/engine.collectionPath'));
|
|
11
|
+
const metadataPath = path.join(testCollectionPath, METADATA_FILE);
|
|
12
|
+
const inventoryPath = path.join(testCollectionPath, INVENTORY_FILE);
|
|
13
|
+
let metadataBackup;
|
|
14
|
+
let getCollection;
|
|
15
|
+
let collection;
|
|
16
|
+
|
|
17
|
+
before(async () => {
|
|
18
|
+
try {
|
|
19
|
+
metadataBackup = await fs.readFile(metadataPath, 'utf8');
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error.code !== 'ENOENT') throw error;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
after(async () => {
|
|
26
|
+
if (metadataBackup) {
|
|
27
|
+
await fs.writeFile(metadataPath, metadataBackup);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
beforeEach(async () => {
|
|
32
|
+
const { getCollection: reloadedGetCollection } = await import(`./index.js?t=${Date.now()}`); // Ensure a new instance is loaded for each test
|
|
33
|
+
|
|
34
|
+
getCollection = reloadedGetCollection;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('singleton behavior', () => {
|
|
38
|
+
beforeEach(async () => {
|
|
39
|
+
collection = await getCollection();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('returns same instance on multiple calls', async () => {
|
|
43
|
+
const collection2 = await getCollection();
|
|
44
|
+
|
|
45
|
+
expect(collection).to.equal(collection2);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('metadata handling', () => {
|
|
50
|
+
const metadata = {
|
|
51
|
+
id: 'test-collection',
|
|
52
|
+
name: 'Test Collection',
|
|
53
|
+
tagline: 'Test collection for testing',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
describe('with existing metadata', () => {
|
|
57
|
+
beforeEach(async () => {
|
|
58
|
+
await fs.mkdir(testCollectionPath, { recursive: true });
|
|
59
|
+
await fs.writeFile(metadataPath, JSON.stringify(metadata));
|
|
60
|
+
collection = await getCollection();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
afterEach(async () => {
|
|
64
|
+
await fs.rm(metadataPath, { recursive: true, force: true });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('loads collection id from metadata', () => {
|
|
68
|
+
expect(collection.id).to.equal(metadata.id);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('loads collection name from metadata', () => {
|
|
72
|
+
expect(collection.name).to.equal(metadata.name);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('loads full metadata object', () => {
|
|
76
|
+
expect(collection.metadata).to.deep.equal(metadata);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('without metadata', () => {
|
|
81
|
+
beforeEach(async () => {
|
|
82
|
+
await fs.rm(metadataPath, { force: true });
|
|
83
|
+
collection = await getCollection();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('has undefined metadata', () => {
|
|
87
|
+
expect(collection.metadata).to.be.undefined;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('has undefined id', () => {
|
|
91
|
+
expect(collection.id).to.be.undefined;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('has undefined name', () => {
|
|
95
|
+
expect(collection.name).to.be.undefined;
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('inventory handling', () => {
|
|
101
|
+
const inventory = {
|
|
102
|
+
all: {
|
|
103
|
+
hosts: {
|
|
104
|
+
'test-host': {
|
|
105
|
+
ansible_host: 'localhost',
|
|
106
|
+
ansible_port: 22,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
describe('with existing inventory', () => {
|
|
113
|
+
beforeEach(async () => {
|
|
114
|
+
await fs.mkdir(path.dirname(inventoryPath), { recursive: true });
|
|
115
|
+
await fs.writeFile(inventoryPath, JSON.stringify(inventory));
|
|
116
|
+
collection = await getCollection();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
afterEach(async () => {
|
|
120
|
+
await fs.rm(inventoryPath, { recursive: true, force: true });
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('loads host from inventory', () => {
|
|
124
|
+
expect(collection.host).to.equal('test-host');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('loads host config from inventory', () => {
|
|
128
|
+
expect(collection.hostConfig).to.deep.equal(inventory.all.hosts['test-host']);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('loads full inventory object', () => {
|
|
132
|
+
expect(collection.inventory).to.deep.equal(inventory);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('without inventory', () => {
|
|
137
|
+
beforeEach(async () => {
|
|
138
|
+
await fs.rm(inventoryPath, { force: true });
|
|
139
|
+
collection = await getCollection();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('has undefined inventory', () => {
|
|
143
|
+
expect(collection.inventory).to.be.undefined;
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('has undefined host', () => {
|
|
147
|
+
expect(collection.host).to.be.undefined;
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('has undefined host config', () => {
|
|
151
|
+
expect(collection.hostConfig).to.be.undefined;
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -49,6 +49,10 @@ export default async function fetch(url, config) {
|
|
|
49
49
|
content,
|
|
50
50
|
};
|
|
51
51
|
} catch (error) {
|
|
52
|
+
if (error.type == 'system') { // Node-fetch wraps system-level errors (ENOTFOUND, ECONNREFUSED, ECONNRESET, etc.) with type 'system' and includes the original error code
|
|
53
|
+
throw new Error(`Network system error ${error.code} occurred when trying to fetch '${url}'`);
|
|
54
|
+
}
|
|
55
|
+
|
|
52
56
|
if (error instanceof AbortError) {
|
|
53
57
|
throw new Error(`Timed out after ${config.navigationTimeout / 1000} seconds when trying to fetch '${url}'`);
|
|
54
58
|
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
|
|
3
|
-
import config from 'config';
|
|
4
1
|
import express from 'express';
|
|
5
2
|
import helmet from 'helmet';
|
|
6
3
|
|
|
4
|
+
import { getCollection } from '../../archivist/collection/index.js';
|
|
7
5
|
import * as Services from '../../archivist/services/index.js';
|
|
8
6
|
|
|
9
7
|
import docsRouter from './docs.js';
|
|
@@ -33,10 +31,10 @@ export default async function apiRouter(basePath) {
|
|
|
33
31
|
res.json({ message: 'Welcome to an instance of the Open Terms Archive API. Documentation is available at /docs. Learn more on Open Terms Archive on https://opentermsarchive.org.' });
|
|
34
32
|
});
|
|
35
33
|
|
|
36
|
-
const collectionPath = path.resolve(process.cwd(), config.get('@opentermsarchive/engine.collectionPath'));
|
|
37
34
|
const services = await Services.load();
|
|
35
|
+
const collection = await getCollection();
|
|
38
36
|
|
|
39
|
-
router.use(await metadataRouter(
|
|
37
|
+
router.use(await metadataRouter(collection, services));
|
|
40
38
|
router.use(servicesRouter(services));
|
|
41
39
|
router.use(versionsRouter);
|
|
42
40
|
|
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
1
|
import express from 'express';
|
|
5
|
-
import yaml from 'js-yaml';
|
|
6
2
|
|
|
7
3
|
import Service from '../../archivist/services/service.js';
|
|
8
4
|
|
|
9
|
-
const METADATA_FILENAME = 'metadata.yml';
|
|
10
|
-
const PACKAGE_JSON_PATH = '../../../package.json';
|
|
11
|
-
|
|
12
5
|
/**
|
|
13
|
-
* @param {
|
|
14
|
-
* @param {object} services
|
|
15
|
-
* @returns {express.Router}
|
|
6
|
+
* @param {object} collection The collection
|
|
7
|
+
* @param {object} services The services of the collection
|
|
8
|
+
* @returns {express.Router} The router instance
|
|
16
9
|
* @swagger
|
|
17
10
|
* tags:
|
|
18
11
|
* name: Metadata
|
|
@@ -175,11 +168,9 @@ const PACKAGE_JSON_PATH = '../../../package.json';
|
|
|
175
168
|
* additionalProperties:
|
|
176
169
|
* type: object
|
|
177
170
|
*/
|
|
178
|
-
export default
|
|
171
|
+
export default function metadataRouter(collection, services) {
|
|
179
172
|
const router = express.Router();
|
|
180
|
-
|
|
181
|
-
const STATIC_METADATA = yaml.load(await fs.readFile(path.join(collectionPath, METADATA_FILENAME), 'utf8'));
|
|
182
|
-
const { version: engineVersion } = JSON.parse(await fs.readFile(new URL(PACKAGE_JSON_PATH, import.meta.url)));
|
|
173
|
+
const engineVersion = process.env.npm_package_version;
|
|
183
174
|
|
|
184
175
|
/**
|
|
185
176
|
* @swagger
|
|
@@ -201,7 +192,7 @@ export default async function metadataRouter(collectionPath, services) {
|
|
|
201
192
|
};
|
|
202
193
|
|
|
203
194
|
res.json({
|
|
204
|
-
...
|
|
195
|
+
...collection.metadata,
|
|
205
196
|
...dynamicMetadata,
|
|
206
197
|
});
|
|
207
198
|
});
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
|
|
3
1
|
import { expect } from 'chai';
|
|
4
2
|
import config from 'config';
|
|
5
3
|
import request from 'supertest';
|
|
@@ -7,7 +5,7 @@ import request from 'supertest';
|
|
|
7
5
|
import app from '../server.js';
|
|
8
6
|
|
|
9
7
|
const basePath = config.get('@opentermsarchive/engine.collection-api.basePath');
|
|
10
|
-
const
|
|
8
|
+
const engineVersion = process.env.npm_package_version;
|
|
11
9
|
|
|
12
10
|
const EXPECTED_RESPONSE = {
|
|
13
11
|
totalServices: 7,
|
package/src/index.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import { createRequire } from 'module';
|
|
2
|
-
|
|
3
1
|
import config from 'config';
|
|
4
2
|
import cron from 'croner';
|
|
5
3
|
import cronstrue from 'cronstrue';
|
|
6
4
|
|
|
5
|
+
import { getCollection } from './archivist/collection/index.js';
|
|
7
6
|
import Archivist from './archivist/index.js';
|
|
8
7
|
import logger from './logger/index.js';
|
|
9
8
|
import Notifier from './notifier/index.js';
|
|
10
9
|
import Reporter from './reporter/index.js';
|
|
11
10
|
|
|
12
|
-
const require = createRequire(import.meta.url);
|
|
13
|
-
|
|
14
11
|
export default async function track({ services, types, extractOnly, schedule }) {
|
|
15
12
|
const archivist = new Archivist({
|
|
16
13
|
recorderConfig: config.get('@opentermsarchive/engine.recorder'),
|
|
@@ -21,9 +18,10 @@ export default async function track({ services, types, extractOnly, schedule })
|
|
|
21
18
|
|
|
22
19
|
await archivist.initialize();
|
|
23
20
|
|
|
24
|
-
const
|
|
21
|
+
const collection = await getCollection();
|
|
22
|
+
const collectionName = collection?.name ? ` with ${collection.name} collection` : '';
|
|
25
23
|
|
|
26
|
-
logger.info(`Start
|
|
24
|
+
logger.info(`Start engine v${process.env.npm_package_version}${collectionName}\n`);
|
|
27
25
|
|
|
28
26
|
if (services?.length) {
|
|
29
27
|
services = services.filter(serviceId => {
|
package/src/logger/index.js
CHANGED
|
@@ -4,10 +4,14 @@ import config from 'config';
|
|
|
4
4
|
import winston from 'winston';
|
|
5
5
|
import 'winston-mail';
|
|
6
6
|
|
|
7
|
+
import { getCollection } from '../archivist/collection/index.js';
|
|
8
|
+
|
|
7
9
|
import { formatDuration } from './utils.js';
|
|
8
10
|
|
|
9
11
|
const { combine, timestamp, printf, colorize } = winston.format;
|
|
10
12
|
|
|
13
|
+
const collection = await getCollection();
|
|
14
|
+
|
|
11
15
|
const alignedWithColorsAndTime = combine(
|
|
12
16
|
colorize(),
|
|
13
17
|
timestamp({ format: 'YYYY-MM-DDTHH:mm:ssZ' }),
|
|
@@ -57,20 +61,97 @@ if (config.get('@opentermsarchive/engine.logger.sendMailOnError')) {
|
|
|
57
61
|
password: process.env.OTA_ENGINE_SMTP_PASSWORD,
|
|
58
62
|
ssl: true,
|
|
59
63
|
timeout: 30 * 1000,
|
|
60
|
-
|
|
64
|
+
html: false,
|
|
65
|
+
formatter({ message, level }) {
|
|
66
|
+
const isError = level.includes('error');
|
|
67
|
+
const titleColor = isError ? '#dc3545' : '#ffc107';
|
|
68
|
+
const titleText = isError ? 'Error details' : 'Warning details';
|
|
69
|
+
|
|
70
|
+
return `
|
|
71
|
+
<!DOCTYPE html>
|
|
72
|
+
<html lang="en">
|
|
73
|
+
<head>
|
|
74
|
+
<meta charset="utf-8">
|
|
75
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
76
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
77
|
+
<title>OTA Error Report</title>
|
|
78
|
+
</head>
|
|
79
|
+
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; color: #333333; max-width: 800px; margin: 0 auto; padding: 0px 20px 20px 20px;">
|
|
80
|
+
<h1 style="color: #212529; font-size: 24px; margin: 10px 0; text-align: center; padding-bottom: 10px;">Open Terms Archive engine error report — ${collection.name} Collection</h1>
|
|
81
|
+
|
|
82
|
+
<div style="background-color: #f8f9fa; border-radius: 8px; padding: 15px; margin-bottom: 0;">
|
|
83
|
+
<h2 style="color: ${titleColor}; margin: 0 0 0 0; font-size: 20px; border-bottom: 2px solid ${titleColor}; padding-bottom: 8px;">${titleText}</h2>
|
|
84
|
+
<div style="background-color: #ffffff; border: 1px solid #dee2e6; border-radius: 4px; padding: 12px; margin: 8px 0;">
|
|
85
|
+
<code style="maring: 0; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; font-size: 14px; color: #212529; white-space: pre-wrap; display: block;">${message}</code>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div style="background-color: #f8f9fa; border-radius: 8px; padding: 15px; margin-bottom: 0;">
|
|
90
|
+
<h2 style="color: #212529; margin: 0 0 0 0; font-size: 20px; border-bottom: 2px solid #212529; padding-bottom: 8px;">System information</h2>
|
|
91
|
+
<div style="color: #6c757d; font-size: 14px; margin: 0;">
|
|
92
|
+
Hostname: ${os.hostname()}<br>
|
|
93
|
+
Platform: ${os.platform()} ${os.release()}<br>
|
|
94
|
+
Architecture: ${os.arch()}<br>
|
|
95
|
+
CPU Cores: ${os.cpus().length}<br>
|
|
96
|
+
CPU Load (1/5/15 min): ${os.loadavg().map(load =>
|
|
97
|
+
`${Math.min(100, (load / os.cpus().length) * 100).toFixed(1)}%`).join(' / ')}<br>
|
|
98
|
+
Total Memory: ${(os.totalmem() / (1024 * 1024 * 1024)).toFixed(2)} GB<br>
|
|
99
|
+
Free Memory: ${(os.freemem() / (1024 * 1024 * 1024)).toFixed(2)} GB${collection.host ? `<br>
|
|
100
|
+
Server IP: ${collection.host}` : ''}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div style="background-color: #f8f9fa; border-radius: 8px; padding: 15px; margin-bottom: 0;">
|
|
105
|
+
<h2 style="color: #198754; margin: 0 0 0 0; font-size: 20px; border-bottom: 2px solid #198754; padding-bottom: 8px;">Helpful commands</h2>
|
|
106
|
+
<ul style="list-style-type: none; padding-left: 0; margin: 0;">
|
|
107
|
+
${collection.host && collection.hostConfig?.ansible_user ? `
|
|
108
|
+
<li style="margin: 0; padding: 0">
|
|
109
|
+
<strong style="display: block; margin-bottom: 0;">Connect to the server:</strong>
|
|
110
|
+
<code style="maring: 0; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; background-color: #ffffff; border: 1px solid #dee2e6; border-radius: 4px; padding: 6px 10px; display: inline-block; color: #212529; cursor: text; user-select: all; -webkit-user-select: all; -moz-user-select: all; -ms-user-select: all;">ssh ${collection.hostConfig.ansible_user}@${collection.host}</code>
|
|
111
|
+
</li>` : ''}
|
|
112
|
+
|
|
113
|
+
<li style="margin: 0; padding: 0">
|
|
114
|
+
<strong style="display: block; margin-bottom: 0;">List processes on the server:</strong>
|
|
115
|
+
<code style="maring: 0; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; background-color: #ffffff; border: 1px solid #dee2e6; border-radius: 4px; padding: 6px 10px; display: inline-block; color: #212529; cursor: text; user-select: all; -webkit-user-select: all; -moz-user-select: all; -ms-user-select: all;">pm2 list</code>
|
|
116
|
+
</li>
|
|
117
|
+
|
|
118
|
+
<li style="margin: 0; padding: 0">
|
|
119
|
+
<strong style="display: block; margin-bottom: 0;">View the logs on the server:</strong>
|
|
120
|
+
<code style="maring: 0; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; background-color: #ffffff; border: 1px solid #dee2e6; border-radius: 4px; padding: 6px 10px; display: inline-block; color: #212529; cursor: text; user-select: all; -webkit-user-select: all; -moz-user-select: all; -ms-user-select: all;">pm2 logs <process-name></code>
|
|
121
|
+
</li>
|
|
122
|
+
|
|
123
|
+
<li style="margin: 0; padding: 0">
|
|
124
|
+
<strong style="display: block; margin-bottom: 0;">View additional logging options:</strong>
|
|
125
|
+
<code style="maring: 0; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; background-color: #ffffff; border: 1px solid #dee2e6; border-radius: 4px; padding: 6px 10px; display: inline-block; color: #212529; cursor: text; user-select: all; -webkit-user-select: all; -moz-user-select: all; -ms-user-select: all;">pm2 logs <process-name> --help</code>
|
|
126
|
+
</li>
|
|
127
|
+
|
|
128
|
+
<li style="margin: 0; padding: 0">
|
|
129
|
+
<strong style="display: block; margin-bottom: 0;">View deployment documentation to see how to start, stop, and restart the server:</strong>
|
|
130
|
+
<a href="https://github.com/OpenTermsArchive/deployment" style="color: #198754; text-decoration: none; border-bottom: 1px solid #198754;">github.com/OpenTermsArchive/deployment</a>
|
|
131
|
+
</li>
|
|
132
|
+
</ul>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #dee2e6; font-size: 12px; color: #6c757d; text-align: center;">
|
|
136
|
+
This is an automated message from the Open Terms Archive engine. Please do not reply to this email.
|
|
137
|
+
</div>
|
|
138
|
+
</body>
|
|
139
|
+
</html>
|
|
140
|
+
`;
|
|
141
|
+
},
|
|
61
142
|
};
|
|
62
143
|
|
|
63
144
|
transports.push(new winston.transports.Mail({
|
|
64
145
|
...mailerOptions,
|
|
65
146
|
level: 'error',
|
|
66
|
-
subject: `
|
|
147
|
+
subject: `Server error on ${collection.id} collection`,
|
|
67
148
|
}));
|
|
68
149
|
|
|
69
150
|
if (config.get('@opentermsarchive/engine.logger.sendMailOnError.sendWarnings')) {
|
|
70
151
|
transports.push(new winston.transports.Mail({
|
|
71
152
|
...mailerOptions,
|
|
72
153
|
level: 'warn',
|
|
73
|
-
subject: `
|
|
154
|
+
subject: `Inaccessible content on ${collection.id} collection`,
|
|
74
155
|
}));
|
|
75
156
|
}
|
|
76
157
|
}
|