@kastov/node-supervisord 2.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.
- package/LICENSE.md +21 -0
- package/README.md +76 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces.d.ts +62 -0
- package/dist/interfaces.js +3 -0
- package/dist/interfaces.js.map +1 -0
- package/dist/methods.d.ts +296 -0
- package/dist/methods.js +50 -0
- package/dist/methods.js.map +1 -0
- package/dist/supervisord-client.d.ts +12 -0
- package/dist/supervisord-client.js +34 -0
- package/dist/supervisord-client.js.map +1 -0
- package/eslint.config.mjs +163 -0
- package/package.json +55 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 bramanda48
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<a name="readme-top"></a>
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
<a href="https://github.com/bramanda48/node-supervisord">
|
|
5
|
+
<img src="https://i.ibb.co/z5VT3Br/supervisord.png" alt="Supervisord" width="150px">
|
|
6
|
+
</a>
|
|
7
|
+
<h2 align="center">node-supervisord</h2>
|
|
8
|
+
<div align="center">
|
|
9
|
+
<p align="center">A Node.js library for communicating with the Supervisord XML-RPC API</p>
|
|
10
|
+
<div>
|
|
11
|
+
<a href="https://github.com/bramanda48/node-supervisord/releases/"><img src="https://img.shields.io/github/release/bramanda48/node-supervisord?include_prereleases=&sort=semver&color=blue" alt="GitHub release"></a>
|
|
12
|
+
<a href="https://github.com/bramanda48/node-supervisord#license"><img src="https://img.shields.io/badge/License-MIT-blue" alt="License"></a>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
## Installation & Usage
|
|
18
|
+
|
|
19
|
+
Install node-supervisord using npm:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install --save node-supervisord
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Install node-supervisord using yarn:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
yarn add node-supervisord
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Example usage :
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { SupervisordClient } from "node-supervisord";
|
|
35
|
+
|
|
36
|
+
// Without authentication options
|
|
37
|
+
const client = new SupervisordClient("http://localhost:9001");
|
|
38
|
+
|
|
39
|
+
// With authentication options
|
|
40
|
+
const client = new SupervisordClient("http://localhost:9001", {
|
|
41
|
+
username: "your-username",
|
|
42
|
+
password: "your-passwword",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Example get api version
|
|
46
|
+
const version = await client.getAPIVersion();
|
|
47
|
+
console.log(version);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
To see the available methods, you can visit [http://supervisord.org/api.html](http://supervisord.org/api.html)
|
|
51
|
+
|
|
52
|
+
## Contributing
|
|
53
|
+
|
|
54
|
+
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!
|
|
55
|
+
|
|
56
|
+
1. Fork the Project
|
|
57
|
+
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
|
|
58
|
+
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
|
|
59
|
+
4. Push to the Branch (`git push origin feature/AmazingFeature`)
|
|
60
|
+
5. Open a Pull Request
|
|
61
|
+
|
|
62
|
+
## Releasing
|
|
63
|
+
|
|
64
|
+
1. Go to [Publish Release](https://github.com/bramanda48/node-supervisord/actions/workflows/publish-release.yaml) page, click Run workflow
|
|
65
|
+
2. Fill the vendor version and then run the flow
|
|
66
|
+
3. GitHub Actions will take care of the rest
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
|
|
70
|
+
This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/bramanda48/node-supervisord/blob/master/LICENSE.md) file for details.
|
|
71
|
+
|
|
72
|
+
## Acknowledgments
|
|
73
|
+
|
|
74
|
+
Inspiration, code snippets, icon, etc.
|
|
75
|
+
|
|
76
|
+
- [Template Typescript](https://github.com/malang-dev/template-typescript) by Malang.dev.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SupervisordClient = exports.SupervisordClientMethod = void 0;
|
|
4
|
+
var methods_1 = require("./methods");
|
|
5
|
+
Object.defineProperty(exports, "SupervisordClientMethod", { enumerable: true, get: function () { return methods_1.SupervisordClientMethod; } });
|
|
6
|
+
var supervisord_client_1 = require("./supervisord-client");
|
|
7
|
+
Object.defineProperty(exports, "SupervisordClient", { enumerable: true, get: function () { return supervisord_client_1.SupervisordClient; } });
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,qCAAoD;AAA3C,kHAAA,uBAAuB,OAAA;AAChC,2DAAyD;AAAhD,uHAAA,iBAAiB,OAAA"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export interface ConfigInfo {
|
|
2
|
+
autostart: boolean;
|
|
3
|
+
command: string;
|
|
4
|
+
exitcodes: number[];
|
|
5
|
+
group: string;
|
|
6
|
+
group_prio: number;
|
|
7
|
+
inuse: boolean;
|
|
8
|
+
killasgroup: boolean;
|
|
9
|
+
name: string;
|
|
10
|
+
process_prio: number;
|
|
11
|
+
redirect_stderr: boolean;
|
|
12
|
+
startretries: number;
|
|
13
|
+
startsecs: number;
|
|
14
|
+
stdout_capture_maxbytes: number;
|
|
15
|
+
stdout_events_enabled: boolean;
|
|
16
|
+
stdout_logfile: string;
|
|
17
|
+
stdout_logfile_backups: number;
|
|
18
|
+
stdout_logfile_maxbytes: number;
|
|
19
|
+
stdout_syslog: boolean;
|
|
20
|
+
stopsignal: number;
|
|
21
|
+
stopwaitsecs: number;
|
|
22
|
+
stderr_capture_maxbytes: number;
|
|
23
|
+
stderr_events_enabled: boolean;
|
|
24
|
+
stderr_logfile: string;
|
|
25
|
+
stderr_logfile_backups: number;
|
|
26
|
+
stderr_logfile_maxbytes: number;
|
|
27
|
+
stderr_syslog: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface ProcessInfo {
|
|
30
|
+
name: string;
|
|
31
|
+
group: string;
|
|
32
|
+
start: number;
|
|
33
|
+
stop: number;
|
|
34
|
+
now: number;
|
|
35
|
+
state: number;
|
|
36
|
+
statename: string;
|
|
37
|
+
spawnerr: string;
|
|
38
|
+
exitstatus: number;
|
|
39
|
+
logfile: string;
|
|
40
|
+
stdout_logfile: string;
|
|
41
|
+
stderr_logfile: string;
|
|
42
|
+
pid: number;
|
|
43
|
+
description: string;
|
|
44
|
+
}
|
|
45
|
+
export interface State {
|
|
46
|
+
state: number;
|
|
47
|
+
statename: string;
|
|
48
|
+
}
|
|
49
|
+
export interface ProcessStatusInfo {
|
|
50
|
+
name: string;
|
|
51
|
+
group: string;
|
|
52
|
+
status: number;
|
|
53
|
+
description: string;
|
|
54
|
+
}
|
|
55
|
+
export interface MulticallArgs {
|
|
56
|
+
methodName: string;
|
|
57
|
+
params: any[];
|
|
58
|
+
}
|
|
59
|
+
export interface MulticallResult {
|
|
60
|
+
faultCode: number;
|
|
61
|
+
faultString: string;
|
|
62
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { ConfigInfo, MulticallArgs, ProcessInfo, ProcessStatusInfo, State } from './interfaces';
|
|
2
|
+
export declare const $SupervisorMethod: string[];
|
|
3
|
+
export declare class SupervisordClientMethod {
|
|
4
|
+
}
|
|
5
|
+
export interface SupervisordClientMethod {
|
|
6
|
+
/**
|
|
7
|
+
* Return the version of the RPC API used by supervisord
|
|
8
|
+
*
|
|
9
|
+
* @return {string}
|
|
10
|
+
*/
|
|
11
|
+
getAPIVersion(): Promise<string>;
|
|
12
|
+
/**
|
|
13
|
+
* @deprecated
|
|
14
|
+
* Return the version of the RPC API used by supervisord
|
|
15
|
+
*
|
|
16
|
+
* @return {string}
|
|
17
|
+
*/
|
|
18
|
+
getVersion(): Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Return the version of the supervisor package in use by supervisord
|
|
21
|
+
*
|
|
22
|
+
* @return {string} version id
|
|
23
|
+
*/
|
|
24
|
+
getSupervisorVersion(): Promise<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Return identifying string of supervisord
|
|
27
|
+
*
|
|
28
|
+
* @return {string} identifying string
|
|
29
|
+
*/
|
|
30
|
+
getIdentification(): Promise<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Return current state of supervisord as a struct
|
|
33
|
+
*
|
|
34
|
+
* @return {State} A struct with keys int statecode, string statename
|
|
35
|
+
*/
|
|
36
|
+
getState(): Promise<State>;
|
|
37
|
+
/**
|
|
38
|
+
* Return the PID of supervisord
|
|
39
|
+
*
|
|
40
|
+
* @return {number} PID
|
|
41
|
+
*/
|
|
42
|
+
getPID(): Promise<number>;
|
|
43
|
+
/**
|
|
44
|
+
* Read length bytes from the main log starting at offset
|
|
45
|
+
*
|
|
46
|
+
* @param {number} offset offset to start reading from
|
|
47
|
+
* @param {number} length number of bytes to read from the log
|
|
48
|
+
* @return {string} Bytes of log
|
|
49
|
+
*/
|
|
50
|
+
readLog(offset: number, length: number): Promise<string>;
|
|
51
|
+
/**
|
|
52
|
+
* Clear the main log
|
|
53
|
+
*
|
|
54
|
+
* @return {boolean} always returns True unless error
|
|
55
|
+
*/
|
|
56
|
+
clearLog(): Promise<boolean>;
|
|
57
|
+
/**
|
|
58
|
+
* Shut down the supervisor process
|
|
59
|
+
*
|
|
60
|
+
* @return {boolean} always returns True unless error
|
|
61
|
+
*/
|
|
62
|
+
shutdown(): Promise<boolean>;
|
|
63
|
+
/**
|
|
64
|
+
* Restart the supervisor process
|
|
65
|
+
*
|
|
66
|
+
* @return {boolean} always returns True unless error
|
|
67
|
+
*/
|
|
68
|
+
restart(): Promise<boolean>;
|
|
69
|
+
/**
|
|
70
|
+
* Get info about a process named name
|
|
71
|
+
*
|
|
72
|
+
* @param {string} name The name of the process (or `group:name`)
|
|
73
|
+
* @return {ProcessInfo} A structure containing data about the process
|
|
74
|
+
*/
|
|
75
|
+
getProcessInfo(name: string): Promise<ProcessInfo>;
|
|
76
|
+
/**
|
|
77
|
+
* Get info about all processes
|
|
78
|
+
*
|
|
79
|
+
* @return {ProcessInfo[]} An array of process status results
|
|
80
|
+
*/
|
|
81
|
+
getAllProcessInfo(): Promise<ProcessInfo[]>;
|
|
82
|
+
/**
|
|
83
|
+
* Get info about all available process configurations. Each struct represents a single process (i.e. groups get flattened)
|
|
84
|
+
*
|
|
85
|
+
* @return {ConfigInfo[]} An array of process config info structs
|
|
86
|
+
*/
|
|
87
|
+
getAllConfigInfo(): Promise<ConfigInfo[]>;
|
|
88
|
+
/**
|
|
89
|
+
* Start a process
|
|
90
|
+
*
|
|
91
|
+
* @param {string} name Process name (or group:name, or group:*)
|
|
92
|
+
* @param {boolean} wait Wait for process to be fully started `(optional)` `(default: true)`
|
|
93
|
+
* @return {boolean} always returns True unless error
|
|
94
|
+
*/
|
|
95
|
+
startProcess(name: string, wait?: boolean): Promise<boolean>;
|
|
96
|
+
/**
|
|
97
|
+
* Start all processes listed in the configuration file
|
|
98
|
+
*
|
|
99
|
+
* @param {boolean} wait Wait for process to be fully started `(optional)` `(default: true)`
|
|
100
|
+
* @return {ProcessStatusInfo[]} An array of process status info structs
|
|
101
|
+
*/
|
|
102
|
+
startAllProcesses(wait?: boolean): Promise<ProcessStatusInfo[]>;
|
|
103
|
+
/**
|
|
104
|
+
* Start all processes in the group named `name`
|
|
105
|
+
*
|
|
106
|
+
* @param {string} name The group name
|
|
107
|
+
* @param {boolean} wait Wait for process to be fully started `(optional)` `(default: true)`
|
|
108
|
+
* @return {ProcessStatusInfo} An array of process status info structs
|
|
109
|
+
*/
|
|
110
|
+
startProcessGroup(name: string, wait?: boolean): Promise<ProcessStatusInfo[]>;
|
|
111
|
+
/**
|
|
112
|
+
* Stop a process named by name
|
|
113
|
+
*
|
|
114
|
+
* @param {string} name The name of the process to stop (or `group:name`)
|
|
115
|
+
* @param {boolean} wait Wait for process to be fully stopped `(optional)` `(default: true)`
|
|
116
|
+
* @return {boolean} always returns True unless error
|
|
117
|
+
*/
|
|
118
|
+
stopProcess(name: string, wait?: boolean): Promise<boolean>;
|
|
119
|
+
/**
|
|
120
|
+
* Stop all processes in the process group named `name`
|
|
121
|
+
*
|
|
122
|
+
* @param {string} name The group name
|
|
123
|
+
* @param {boolean} wait Wait for process to be fully stopped `(optional)` `(default: true)`
|
|
124
|
+
* @return {ProcessStatusInfo[]} always returns True unless error
|
|
125
|
+
*/
|
|
126
|
+
stopProcessGroup(name: string, wait?: boolean): Promise<ProcessStatusInfo[]>;
|
|
127
|
+
/**
|
|
128
|
+
* Stop all processes in the process list
|
|
129
|
+
*
|
|
130
|
+
* @param {boolean} wait Wait for process to be fully started `(optional)` `(default: true)`
|
|
131
|
+
* @return {ProcessStatusInfo[]} An array of process status info structs
|
|
132
|
+
*/
|
|
133
|
+
stopAllProcesses(wait?: boolean): Promise<ProcessStatusInfo[]>;
|
|
134
|
+
/**
|
|
135
|
+
* Send an arbitrary UNIX signal to the process named by name
|
|
136
|
+
*
|
|
137
|
+
* @param {string} name Name of the process to signal (or `group:name`)
|
|
138
|
+
* @param {string | number} signal Signal to send, as name (`HUP`) or number (`1`)
|
|
139
|
+
* @return {boolean} true on success
|
|
140
|
+
*/
|
|
141
|
+
signalProcess(name: string, signal: string | number): Promise<boolean>;
|
|
142
|
+
/**
|
|
143
|
+
* Send a signal to all processes in the group named `name`
|
|
144
|
+
*
|
|
145
|
+
* @param {string} name The group name
|
|
146
|
+
* @param {string | number} signal Signal to send, as name (`HUP`) or number (`1`)
|
|
147
|
+
* @return {ProcessStatusInfo[]} An array of process status info structs
|
|
148
|
+
*/
|
|
149
|
+
signalProcessGroup(name: string, signal: string | number): Promise<ProcessStatusInfo[]>;
|
|
150
|
+
/**
|
|
151
|
+
* Send a string of chars to the stdin of the process name
|
|
152
|
+
* - If non-7-bit data is sent (unicode), it is encoded to utf-8 before being sent to the process’ stdin
|
|
153
|
+
* - If chars is not a string or is not unicode, raise INCORRECT_PARAMETERS
|
|
154
|
+
* - If the process is not running, raise NOT_RUNNING
|
|
155
|
+
* - If the process’ stdin cannot accept input (e.g. it was closed by the child process), raise NO_FILE
|
|
156
|
+
*
|
|
157
|
+
* @param {string} name The process name to send to (or `group:name`)
|
|
158
|
+
* @param {string} chars The character data to send to the process
|
|
159
|
+
* @return {boolean} always returns True unless error
|
|
160
|
+
*/
|
|
161
|
+
sendProcessStdin(name: string, chars: string): Promise<boolean>;
|
|
162
|
+
/**
|
|
163
|
+
* Send an event that will be received by event listener subprocesses subscribing to the RemoteCommunicationEvent
|
|
164
|
+
*
|
|
165
|
+
* @param {string} type String for the `type` key in the event header
|
|
166
|
+
* @param {string} data Data for the event body
|
|
167
|
+
* @return {boolean} always returns True unless error
|
|
168
|
+
*/
|
|
169
|
+
sendRemoteCommEvent(type: string, data: string): Promise<boolean>;
|
|
170
|
+
/**
|
|
171
|
+
* Reload the configuration. The result contains three arrays containing names of process groups:
|
|
172
|
+
* - `added` gives the process groups that have been added
|
|
173
|
+
* - `changed` gives the process groups whose contents have changed
|
|
174
|
+
* - `removed` gives the process groups that are no longer in the configuration
|
|
175
|
+
*
|
|
176
|
+
* @return {any} `[[added, changed, removed]]`
|
|
177
|
+
*/
|
|
178
|
+
reloadConfig(): Promise<any>;
|
|
179
|
+
/**
|
|
180
|
+
* Update the config for a running process from config file
|
|
181
|
+
*
|
|
182
|
+
* @param {string} name Name of process group to add
|
|
183
|
+
* @return {boolean} true on success
|
|
184
|
+
*/
|
|
185
|
+
addProcessGroup(name: string): Promise<boolean>;
|
|
186
|
+
/**
|
|
187
|
+
* Remove a stopped process from the active configuration
|
|
188
|
+
*
|
|
189
|
+
* @param {string} name Name of process group to remove
|
|
190
|
+
* @return {boolean} Indicates whether the removal was successful
|
|
191
|
+
*/
|
|
192
|
+
removeProcessGroup(name: string): Promise<boolean>;
|
|
193
|
+
/**
|
|
194
|
+
* Read length bytes from name’s stdout log starting at offset
|
|
195
|
+
*
|
|
196
|
+
* @param {string} name The name of the process (or `group:name`)
|
|
197
|
+
* @param {number} offset to start reading from
|
|
198
|
+
* @param {number} length Number of bytes to read from the log
|
|
199
|
+
* @return {string} Bytes of log
|
|
200
|
+
*/
|
|
201
|
+
readProcessStdoutLog(name: string, offset: number, length: number): Promise<string>;
|
|
202
|
+
/**
|
|
203
|
+
* Read length bytes from name’s stderr log starting at offset
|
|
204
|
+
*
|
|
205
|
+
* @param {string} name The name of the process (or `group:name`)
|
|
206
|
+
* @param {number} offset to start reading from
|
|
207
|
+
* @param {number} length Number of bytes to read from the log
|
|
208
|
+
* @return {string} Bytes of log
|
|
209
|
+
*/
|
|
210
|
+
readProcessStderrLog(name: string, offset: number, length: number): Promise<string>;
|
|
211
|
+
/**
|
|
212
|
+
* Provides a more efficient way to tail the (stdout) log than readProcessStdoutLog().
|
|
213
|
+
* Use readProcessStdoutLog() to read chunks and tailProcessStdoutLog() to tail.
|
|
214
|
+
*
|
|
215
|
+
* Requests (length) bytes from the (name)'s log, starting at (offset).
|
|
216
|
+
* - If the total log size is greater than (offset + length),
|
|
217
|
+
* the overflow flag is set and the (offset) is automatically increased to position the buffer at the end of the log.
|
|
218
|
+
* - If less than (length) bytes are available, the maximum number of available bytes will be returned.
|
|
219
|
+
* (offset) returned is always the last offset in the log +1.
|
|
220
|
+
*
|
|
221
|
+
* @param {string} name The name of the process (or `group:name`)
|
|
222
|
+
* @param {number} offset to start reading from
|
|
223
|
+
* @param {number} length Maximum number of bytes to return
|
|
224
|
+
* @return {any} `[string bytes, int offset, bool overflow]`
|
|
225
|
+
*/
|
|
226
|
+
tailProcessStdoutLog(name: string, offset: number, length: number): Promise<any>;
|
|
227
|
+
/**
|
|
228
|
+
* Provides a more efficient way to tail the (stderr) log than readProcessStderrLog().
|
|
229
|
+
* Use readProcessStderrLog() to read chunks and tailProcessStderrLog() to tail.
|
|
230
|
+
*
|
|
231
|
+
* Requests (length) bytes from the (name)'s log, starting at (offset).
|
|
232
|
+
* - If the total log size is greater than (offset + length),
|
|
233
|
+
* the overflow flag is set and the (offset) is automatically increased to position the buffer at the end of the log.
|
|
234
|
+
* - If less than (length) bytes are available, the maximum number of available bytes will be returned.
|
|
235
|
+
* (offset) returned is always the last offset in the log +1.
|
|
236
|
+
*
|
|
237
|
+
* @param {string} name The name of the process (or `group:name`)
|
|
238
|
+
* @param {number} offset to start reading from
|
|
239
|
+
* @param {number} length Maximum number of bytes to return
|
|
240
|
+
* @return {any} `[string bytes, int offset, bool overflow]`
|
|
241
|
+
*/
|
|
242
|
+
tailProcessStderrLog(name: string, offset: number, length: number): Promise<any>;
|
|
243
|
+
/**
|
|
244
|
+
* Clear the stdout and stderr logs for the named process and reopen them
|
|
245
|
+
*
|
|
246
|
+
* @param {string} name The name of the process (or `group:name`)
|
|
247
|
+
* @return {boolean} always returns True unless error
|
|
248
|
+
*/
|
|
249
|
+
clearProcessLogs(name: string): Promise<boolean>;
|
|
250
|
+
/**
|
|
251
|
+
* Clear the stdout and stderr logs for the named process and reopen them
|
|
252
|
+
*
|
|
253
|
+
* @param {string} name The name of the process (or `group:name`)
|
|
254
|
+
* @return {boolean} always returns True unless error
|
|
255
|
+
*/
|
|
256
|
+
clearProcessLog(name: string): Promise<boolean>;
|
|
257
|
+
/**
|
|
258
|
+
* Clear all process log files
|
|
259
|
+
*
|
|
260
|
+
* @return {ProcessStatusInfo[]} An array of process status info structs
|
|
261
|
+
*/
|
|
262
|
+
clearAllProcessLogs(): Promise<ProcessStatusInfo[]>;
|
|
263
|
+
/**
|
|
264
|
+
* Return an array listing the available method names
|
|
265
|
+
*
|
|
266
|
+
* @return {string[]} An array of method names available (strings)
|
|
267
|
+
*/
|
|
268
|
+
listMethods(): Promise<string[]>;
|
|
269
|
+
/**
|
|
270
|
+
* Return a string showing the method's documentation
|
|
271
|
+
*
|
|
272
|
+
* @param {string} name The name of the method
|
|
273
|
+
* @return {string} The documentation for the method name
|
|
274
|
+
*/
|
|
275
|
+
methodHelp(name: string): Promise<string>;
|
|
276
|
+
/**
|
|
277
|
+
* Return an array describing the method signature in the form [rtype, ptype, ptype…]
|
|
278
|
+
* where rtype is the return data type of the method, and ptypes are the parameter data types
|
|
279
|
+
* that the method accepts in method argument order
|
|
280
|
+
*
|
|
281
|
+
* @param {string} name The name of the method
|
|
282
|
+
* @return {string[]} The result
|
|
283
|
+
*/
|
|
284
|
+
methodSignature(name: string): Promise<string[]>;
|
|
285
|
+
/**
|
|
286
|
+
* Process an array of calls, and return an array of results.
|
|
287
|
+
* Calls should be structs of the form {'methodName': string, 'params': array}.
|
|
288
|
+
* Each result will either be a single-item array containing the result value,
|
|
289
|
+
* or a struct of the form {'faultCode': int, 'faultString': string}.
|
|
290
|
+
* This is useful when you need to make lots of small calls without lots of round trips.
|
|
291
|
+
*
|
|
292
|
+
* @param {MulticallArgs[]} calls An array of call requests
|
|
293
|
+
* @return {any} The result
|
|
294
|
+
*/
|
|
295
|
+
multicall(calls: MulticallArgs[]): Promise<any>;
|
|
296
|
+
}
|
package/dist/methods.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SupervisordClientMethod = exports.$SupervisorMethod = void 0;
|
|
4
|
+
exports.$SupervisorMethod = [
|
|
5
|
+
'supervisor.addProcessGroup',
|
|
6
|
+
'supervisor.clearAllProcessLogs',
|
|
7
|
+
'supervisor.clearLog',
|
|
8
|
+
'supervisor.clearProcessLog',
|
|
9
|
+
'supervisor.clearProcessLogs',
|
|
10
|
+
'supervisor.getAPIVersion',
|
|
11
|
+
'supervisor.getAllConfigInfo',
|
|
12
|
+
'supervisor.getAllProcessInfo',
|
|
13
|
+
'supervisor.getIdentification',
|
|
14
|
+
'supervisor.getPID',
|
|
15
|
+
'supervisor.getProcessInfo',
|
|
16
|
+
'supervisor.getState',
|
|
17
|
+
'supervisor.getSupervisorVersion',
|
|
18
|
+
'supervisor.getVersion', // deprecated
|
|
19
|
+
'supervisor.readLog',
|
|
20
|
+
'supervisor.readMainLog', // deprecated
|
|
21
|
+
'supervisor.readProcessLog', // deprecated
|
|
22
|
+
'supervisor.readProcessStderrLog',
|
|
23
|
+
'supervisor.readProcessStdoutLog',
|
|
24
|
+
'supervisor.reloadConfig',
|
|
25
|
+
'supervisor.removeProcessGroup',
|
|
26
|
+
'supervisor.restart',
|
|
27
|
+
'supervisor.sendProcessStdin',
|
|
28
|
+
'supervisor.sendRemoteCommEvent',
|
|
29
|
+
'supervisor.signalProcess',
|
|
30
|
+
'supervisor.signalProcessGroup',
|
|
31
|
+
'supervisor.shutdown',
|
|
32
|
+
'supervisor.startAllProcesses',
|
|
33
|
+
'supervisor.startProcess',
|
|
34
|
+
'supervisor.startProcessGroup',
|
|
35
|
+
'supervisor.stopAllProcesses',
|
|
36
|
+
'supervisor.stopProcess',
|
|
37
|
+
'supervisor.stopProcessGroup',
|
|
38
|
+
'supervisor.tailProcessLog', // deprecated
|
|
39
|
+
'supervisor.tailProcessStderrLog',
|
|
40
|
+
'supervisor.tailProcessStdoutLog',
|
|
41
|
+
'system.listMethods',
|
|
42
|
+
'system.methodHelp',
|
|
43
|
+
'system.methodSignature',
|
|
44
|
+
'system.multicall',
|
|
45
|
+
];
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
47
|
+
class SupervisordClientMethod {
|
|
48
|
+
}
|
|
49
|
+
exports.SupervisordClientMethod = SupervisordClientMethod;
|
|
50
|
+
//# sourceMappingURL=methods.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"methods.js","sourceRoot":"","sources":["../src/methods.ts"],"names":[],"mappings":";;;AAEa,QAAA,iBAAiB,GAAG;IAC7B,4BAA4B;IAC5B,gCAAgC;IAChC,qBAAqB;IACrB,4BAA4B;IAC5B,6BAA6B;IAC7B,0BAA0B;IAC1B,6BAA6B;IAC7B,8BAA8B;IAC9B,8BAA8B;IAC9B,mBAAmB;IACnB,2BAA2B;IAC3B,qBAAqB;IACrB,iCAAiC;IACjC,uBAAuB,EAAE,aAAa;IACtC,oBAAoB;IACpB,wBAAwB,EAAE,aAAa;IACvC,2BAA2B,EAAE,aAAa;IAC1C,iCAAiC;IACjC,iCAAiC;IACjC,yBAAyB;IACzB,+BAA+B;IAC/B,oBAAoB;IACpB,6BAA6B;IAC7B,gCAAgC;IAChC,0BAA0B;IAC1B,+BAA+B;IAC/B,qBAAqB;IACrB,8BAA8B;IAC9B,yBAAyB;IACzB,8BAA8B;IAC9B,6BAA6B;IAC7B,wBAAwB;IACxB,6BAA6B;IAC7B,2BAA2B,EAAE,aAAa;IAC1C,iCAAiC;IACjC,iCAAiC;IACjC,oBAAoB;IACpB,mBAAmB;IACnB,wBAAwB;IACxB,kBAAkB;CACrB,CAAC;AAEF,4EAA4E;AAC5E,MAAa,uBAAuB;CAAG;AAAvC,0DAAuC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { XmlRpcValue } from '@foxglove/xmlrpc';
|
|
2
|
+
import { SupervisordClientMethod } from './methods';
|
|
3
|
+
export interface SupervisordClientOptions {
|
|
4
|
+
username: string;
|
|
5
|
+
password: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class SupervisordClient extends SupervisordClientMethod {
|
|
8
|
+
private client;
|
|
9
|
+
constructor(connectionUrl: string, options?: SupervisordClientOptions);
|
|
10
|
+
_call(method: string, params: XmlRpcValue[]): Promise<XmlRpcValue>;
|
|
11
|
+
private createHeaders;
|
|
12
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SupervisordClient = void 0;
|
|
4
|
+
const xmlrpc_1 = require("@foxglove/xmlrpc");
|
|
5
|
+
const methods_1 = require("./methods");
|
|
6
|
+
class SupervisordClient extends methods_1.SupervisordClientMethod {
|
|
7
|
+
client;
|
|
8
|
+
constructor(connectionUrl, options) {
|
|
9
|
+
super();
|
|
10
|
+
this.client = new xmlrpc_1.XmlRpcClient(connectionUrl, {
|
|
11
|
+
encoding: 'utf-8',
|
|
12
|
+
headers: this.createHeaders(options),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
async _call(method, params) {
|
|
16
|
+
return this.client.methodCall(method, params);
|
|
17
|
+
}
|
|
18
|
+
createHeaders(options) {
|
|
19
|
+
if (!options) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const { username, password } = options;
|
|
23
|
+
const encoded = Buffer.from(username + ':' + password).toString('base64');
|
|
24
|
+
return { Authorization: `Basic ${encoded}` };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.SupervisordClient = SupervisordClient;
|
|
28
|
+
methods_1.$SupervisorMethod.forEach((method) => {
|
|
29
|
+
const methodName = method.split('.').pop();
|
|
30
|
+
SupervisordClient.prototype[methodName] = function (...params) {
|
|
31
|
+
return this._call(method, params);
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
//# sourceMappingURL=supervisord-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisord-client.js","sourceRoot":"","sources":["../src/supervisord-client.ts"],"names":[],"mappings":";;;AAAA,6CAA6D;AAE7D,uCAAuE;AAOvE,MAAa,iBAAkB,SAAQ,iCAAuB;IAClD,MAAM,CAAe;IAE7B,YAAY,aAAqB,EAAE,OAAkC;QACjE,KAAK,EAAE,CAAC;QAER,IAAI,CAAC,MAAM,GAAG,IAAI,qBAAY,CAAC,aAAa,EAAE;YAC1C,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC;SACvC,CAAC,CAAC;IACP,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,MAAc,EAAE,MAAqB;QAC7C,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClD,CAAC;IAEO,aAAa,CAAC,OAAkC;QACpD,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,OAAO,SAAS,CAAC;QACrB,CAAC;QAED,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;QACvC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,GAAG,GAAG,GAAG,QAAQ,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC1E,OAAO,EAAE,aAAa,EAAE,SAAS,OAAO,EAAE,EAAE,CAAC;IACjD,CAAC;CACJ;AAzBD,8CAyBC;AAED,2BAAiB,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;IACjC,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;IAC1C,iBAAiB,CAAC,SAAiB,CAAC,UAAU,CAAC,GAAG,UAAU,GAAG,MAAqB;QACjF,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC,CAAC;AACN,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import typescriptEslintEslintPlugin from '@typescript-eslint/eslint-plugin';
|
|
2
|
+
import perfectionist from 'eslint-plugin-perfectionist';
|
|
3
|
+
import tsParser from '@typescript-eslint/parser';
|
|
4
|
+
import { FlatCompat } from '@eslint/eslintrc';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import paths from 'eslint-plugin-paths';
|
|
7
|
+
import globals from 'globals';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
const compat = new FlatCompat({
|
|
13
|
+
baseDirectory: __dirname,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export default [
|
|
17
|
+
{
|
|
18
|
+
ignores: ['**/.eslintrc.js', 'prisma/**/*', '.hygen.js', '.hygen/**/*'],
|
|
19
|
+
},
|
|
20
|
+
...compat.extends('plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'),
|
|
21
|
+
perfectionist.configs['recommended-alphabetical'],
|
|
22
|
+
{
|
|
23
|
+
plugins: {
|
|
24
|
+
'@typescript-eslint': typescriptEslintEslintPlugin,
|
|
25
|
+
paths,
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
languageOptions: {
|
|
29
|
+
globals: {
|
|
30
|
+
...globals.node,
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
parser: tsParser,
|
|
34
|
+
ecmaVersion: 'latest',
|
|
35
|
+
sourceType: 'commonjs',
|
|
36
|
+
|
|
37
|
+
parserOptions: {
|
|
38
|
+
project: 'tsconfig.json',
|
|
39
|
+
tsconfigRootDir: __dirname,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
rules: {
|
|
44
|
+
'perfectionist/sort-imports': [
|
|
45
|
+
'error',
|
|
46
|
+
{
|
|
47
|
+
type: 'line-length',
|
|
48
|
+
order: 'desc',
|
|
49
|
+
ignoreCase: true,
|
|
50
|
+
specialCharacters: 'keep',
|
|
51
|
+
internalPattern: ['^~/.+'],
|
|
52
|
+
tsconfigRootDir: '.',
|
|
53
|
+
partitionByComment: false,
|
|
54
|
+
partitionByNewLine: false,
|
|
55
|
+
newlinesBetween: 'always',
|
|
56
|
+
maxLineLength: undefined,
|
|
57
|
+
tsconfigRootDir: __dirname,
|
|
58
|
+
|
|
59
|
+
groups: [
|
|
60
|
+
'type',
|
|
61
|
+
['builtin', 'external'],
|
|
62
|
+
'internal-type',
|
|
63
|
+
'internal',
|
|
64
|
+
'nestJs',
|
|
65
|
+
'remnawave',
|
|
66
|
+
'aliasCommon',
|
|
67
|
+
{ newlinesBetween: 'never' },
|
|
68
|
+
'aliasLibs',
|
|
69
|
+
'aliasIntegrationModules',
|
|
70
|
+
'aliasModules',
|
|
71
|
+
'aliasScheduler',
|
|
72
|
+
'aliasQueue',
|
|
73
|
+
['parent-type', 'sibling-type', 'index-type'],
|
|
74
|
+
['parent', 'sibling', 'index'],
|
|
75
|
+
'object',
|
|
76
|
+
'unknown',
|
|
77
|
+
],
|
|
78
|
+
|
|
79
|
+
customGroups: {
|
|
80
|
+
value: {
|
|
81
|
+
aliasModules: '@modules/*.',
|
|
82
|
+
aliasCommon: '@common/*.',
|
|
83
|
+
aliasLibs: '@libs/*.',
|
|
84
|
+
aliasIntegrationModules: '@integration-modules/*.',
|
|
85
|
+
aliasScheduler: '@scheduler/*.',
|
|
86
|
+
aliasQueue: '@queue/*.',
|
|
87
|
+
remnawave: '@remnawave/*.',
|
|
88
|
+
nestJs: '@nestjs/*.',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
environment: 'node',
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
'perfectionist/sort-decorators': [
|
|
96
|
+
'error',
|
|
97
|
+
{
|
|
98
|
+
groups: [
|
|
99
|
+
'unknown',
|
|
100
|
+
'httpCodes',
|
|
101
|
+
'filters',
|
|
102
|
+
'guards',
|
|
103
|
+
'controllers',
|
|
104
|
+
'nestJSMethods',
|
|
105
|
+
],
|
|
106
|
+
|
|
107
|
+
customGroups: {
|
|
108
|
+
httpCodes: ['HttpCode'],
|
|
109
|
+
filters: ['UseFilters'],
|
|
110
|
+
guards: ['UseGuards'],
|
|
111
|
+
controllers: ['Controller'],
|
|
112
|
+
nestJSMethods: ['Post', 'Get', 'Put', 'Delete', 'Patch', 'Options', 'Head'],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
|
|
117
|
+
'perfectionist/sort-objects': ['off'],
|
|
118
|
+
'perfectionist/sort-classes': ['off'],
|
|
119
|
+
'perfectionist/sort-switch-case': ['off'],
|
|
120
|
+
'perfectionist/sort-object-types': ['off'],
|
|
121
|
+
'perfectionist/sort-interfaces': ['off'],
|
|
122
|
+
'perfectionist/sort-union-types': ['off'],
|
|
123
|
+
'perfectionist/sort-named-imports': ['off'],
|
|
124
|
+
'perfectionist/sort-modules': ['off'],
|
|
125
|
+
'paths/alias': 'error',
|
|
126
|
+
'@typescript-eslint/interface-name-prefix': 'off',
|
|
127
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
128
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
129
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
130
|
+
'@typescript-eslint/no-namespace': 'off',
|
|
131
|
+
'linebreak-style': 0,
|
|
132
|
+
'no-console': 'warn',
|
|
133
|
+
|
|
134
|
+
'prettier/prettier': [
|
|
135
|
+
'error',
|
|
136
|
+
{
|
|
137
|
+
bracketSpacing: true,
|
|
138
|
+
tabWidth: 4,
|
|
139
|
+
printWidth: 100,
|
|
140
|
+
singleQuote: true,
|
|
141
|
+
trailingComma: 'all',
|
|
142
|
+
|
|
143
|
+
overrides: [
|
|
144
|
+
{
|
|
145
|
+
files: ['*.js', '*.jsx', '*.ts', '*.tsx'],
|
|
146
|
+
|
|
147
|
+
options: {
|
|
148
|
+
parser: 'typescript',
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
files: ['*.md', '*.json', '*.yaml', '*.yml'],
|
|
153
|
+
|
|
154
|
+
options: {
|
|
155
|
+
tabWidth: 2,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kastov/node-supervisord",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "A Node.js library for communicating with the Supervisord XML-RPC API",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"supervisor",
|
|
7
|
+
"supervisord",
|
|
8
|
+
"sdk",
|
|
9
|
+
"typescript",
|
|
10
|
+
"module",
|
|
11
|
+
"addon"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/kastov/node-supervisord",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+ssh://git@github.com/kastov/node-supervisord.git"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"private": false,
|
|
20
|
+
"main": "dist/index.js",
|
|
21
|
+
"types": "dist/index.d.ts",
|
|
22
|
+
"scripts": {
|
|
23
|
+
"prepare": "husky",
|
|
24
|
+
"prebuild": "rimraf dist",
|
|
25
|
+
"format": "prettier --write .",
|
|
26
|
+
"build": "tsc -b tsconfig.build.json",
|
|
27
|
+
"lint": "eslint \"src/**/*.ts\" --fix",
|
|
28
|
+
"lint-staged": "lint-staged"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"husky": "^9.0.11",
|
|
32
|
+
"lint-staged": "^15.2.2",
|
|
33
|
+
"prettier-plugin-organize-imports": "^3.2.4",
|
|
34
|
+
"rimraf": "^5.0.5",
|
|
35
|
+
"@types/node": "^24.10.1",
|
|
36
|
+
"@typescript-eslint/eslint-plugin": "8.49.0",
|
|
37
|
+
"@typescript-eslint/parser": "8.49.0",
|
|
38
|
+
"eslint": "9.39.1",
|
|
39
|
+
"eslint-config-prettier": "^10.1.8",
|
|
40
|
+
"eslint-plugin-paths": "^1.1.0",
|
|
41
|
+
"eslint-plugin-perfectionist": "^4.15.1",
|
|
42
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
43
|
+
"prettier": "^3.7.4",
|
|
44
|
+
"typescript": "^5.9.3"
|
|
45
|
+
},
|
|
46
|
+
"lint-staged": {
|
|
47
|
+
"src/**/*.{ts|test.ts}": [
|
|
48
|
+
"eslint --max-warnings 0 \"src/**/*.ts\"",
|
|
49
|
+
"prettier --write --ignore-unknown"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@foxglove/xmlrpc": "^1.3.0"
|
|
54
|
+
}
|
|
55
|
+
}
|