@kapeta/local-cluster-service 0.8.0 → 0.8.2
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/CHANGELOG.md +14 -0
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.js +34 -0
- package/dist/cjs/src/attachments/routes.js +0 -2
- package/dist/cjs/src/containerManager.d.ts +1 -0
- package/dist/cjs/src/containerManager.js +19 -0
- package/dist/cjs/src/socketManager.d.ts +1 -0
- package/dist/cjs/src/socketManager.js +3 -3
- package/dist/cjs/start.js +1 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +34 -0
- package/dist/esm/src/attachments/routes.js +0 -2
- package/dist/esm/src/containerManager.d.ts +1 -0
- package/dist/esm/src/containerManager.js +19 -0
- package/dist/esm/src/socketManager.d.ts +1 -0
- package/dist/esm/src/socketManager.js +3 -3
- package/dist/esm/start.js +1 -0
- package/index.ts +40 -0
- package/package.json +4 -4
- package/src/attachments/routes.ts +0 -2
- package/src/containerManager.ts +20 -0
- package/src/socketManager.ts +4 -4
- package/start.ts +1 -0
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## [0.8.2](https://github.com/kapetacom/local-cluster-service/compare/v0.8.1...v0.8.2) (2023-07-23)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* Check for existing cluster services on the same point and exit if it exists ([#47](https://github.com/kapetacom/local-cluster-service/issues/47)) ([3c76c3c](https://github.com/kapetacom/local-cluster-service/commit/3c76c3c24927212e4a06dc627e6c7262b2c60c4f))
|
7
|
+
|
8
|
+
## [0.8.1](https://github.com/kapetacom/local-cluster-service/compare/v0.8.0...v0.8.1) (2023-07-22)
|
9
|
+
|
10
|
+
|
11
|
+
### Bug Fixes
|
12
|
+
|
13
|
+
* Removed debug ([e002caa](https://github.com/kapetacom/local-cluster-service/commit/e002caa3ae20c5a9a7f521e6e47bf46abfcb9e96))
|
14
|
+
|
1
15
|
# [0.8.0](https://github.com/kapetacom/local-cluster-service/compare/v0.7.6...v0.8.0) (2023-07-22)
|
2
16
|
|
3
17
|
|
package/dist/cjs/index.d.ts
CHANGED
@@ -12,6 +12,9 @@ export type StartResult = {
|
|
12
12
|
declare const _default: {
|
13
13
|
isRunning: () => boolean;
|
14
14
|
getCurrentPort: () => number | undefined;
|
15
|
+
ping: (host: string, port: number) => Promise<{
|
16
|
+
ok: boolean;
|
17
|
+
}>;
|
15
18
|
/**
|
16
19
|
* Starts the local cluster service.
|
17
20
|
* resolves when listening is done with port number. Rejects if listening failed.
|
package/dist/cjs/index.js
CHANGED
@@ -21,6 +21,7 @@ const routes_7 = __importDefault(require("./src/assets/routes"));
|
|
21
21
|
const routes_8 = __importDefault(require("./src/providers/routes"));
|
22
22
|
const routes_9 = __importDefault(require("./src/attachments/routes"));
|
23
23
|
const utils_1 = require("./src/utils/utils");
|
24
|
+
const request_1 = __importDefault(require("request"));
|
24
25
|
let currentServer = null;
|
25
26
|
function createServer() {
|
26
27
|
const app = (0, express_1.default)();
|
@@ -33,6 +34,18 @@ function createServer() {
|
|
33
34
|
app.use('/assets', routes_7.default);
|
34
35
|
app.use('/providers', routes_8.default);
|
35
36
|
app.use('/attachments', routes_9.default);
|
37
|
+
app.get('/status', async (req, res) => {
|
38
|
+
res.send({
|
39
|
+
ok: true,
|
40
|
+
dockerStatus: await containerManager_1.containerManager.checkAlive(),
|
41
|
+
socketStatus: socketManager_1.socketManager.isAlive()
|
42
|
+
});
|
43
|
+
});
|
44
|
+
app.get('/ping', async (req, res) => {
|
45
|
+
res.send({
|
46
|
+
ok: true
|
47
|
+
});
|
48
|
+
});
|
36
49
|
app.use('/', (req, res) => {
|
37
50
|
console.error('Invalid request: %s %s', req.method, req.originalUrl);
|
38
51
|
res.status(400).send({
|
@@ -61,6 +74,17 @@ exports.default = {
|
|
61
74
|
}
|
62
75
|
return currentServer.port;
|
63
76
|
},
|
77
|
+
ping: async function (host, port) {
|
78
|
+
return new Promise((resolve, reject) => {
|
79
|
+
request_1.default.get(`http://${host}:${port}/ping`, (err, res, body) => {
|
80
|
+
if (err) {
|
81
|
+
reject(err);
|
82
|
+
return;
|
83
|
+
}
|
84
|
+
resolve(JSON.parse(body));
|
85
|
+
});
|
86
|
+
});
|
87
|
+
},
|
64
88
|
/**
|
65
89
|
* Starts the local cluster service.
|
66
90
|
* resolves when listening is done with port number. Rejects if listening failed.
|
@@ -83,6 +107,16 @@ exports.default = {
|
|
83
107
|
if (clusterHost) {
|
84
108
|
clusterService_1.clusterService.setClusterServiceHost(clusterHost);
|
85
109
|
}
|
110
|
+
let pingResult = undefined;
|
111
|
+
try {
|
112
|
+
pingResult = await this.ping(clusterHost, clusterPort);
|
113
|
+
}
|
114
|
+
catch (e) {
|
115
|
+
//Ignore - expected to not be running since we're starting it
|
116
|
+
}
|
117
|
+
if (pingResult?.ok) {
|
118
|
+
throw new Error(`Cluster service already running on: ${clusterHost}:${clusterPort}.`);
|
119
|
+
}
|
86
120
|
await clusterService_1.clusterService.init();
|
87
121
|
currentServer = createServer();
|
88
122
|
const port = clusterService_1.clusterService.getClusterServicePort();
|
@@ -41,7 +41,6 @@ router.put('/:handle/:name', async (req, res) => {
|
|
41
41
|
try {
|
42
42
|
const { handle, name } = req.params;
|
43
43
|
const url = `${endpoint}/${handle}/${name}/attachments`;
|
44
|
-
console.log('Sending PUT', url);
|
45
44
|
const result = await api.send({
|
46
45
|
method: 'PUT',
|
47
46
|
url,
|
@@ -53,7 +52,6 @@ router.put('/:handle/:name', async (req, res) => {
|
|
53
52
|
},
|
54
53
|
body: req
|
55
54
|
});
|
56
|
-
console.log('Got result from upload', result);
|
57
55
|
res.send(result);
|
58
56
|
}
|
59
57
|
catch (e) {
|
@@ -30,6 +30,7 @@ declare class ContainerManager {
|
|
30
30
|
private _version;
|
31
31
|
constructor();
|
32
32
|
initialize(): Promise<void>;
|
33
|
+
checkAlive(): Promise<boolean>;
|
33
34
|
isAlive(): boolean;
|
34
35
|
getMountPoint(kind: string, mountName: string): string;
|
35
36
|
createMounts(kind: string, mountOpts: StringMap): StringMap;
|
@@ -77,6 +77,25 @@ class ContainerManager {
|
|
77
77
|
}
|
78
78
|
throw new Error('Could not connect to docker daemon. Please make sure docker is running and working.');
|
79
79
|
}
|
80
|
+
async checkAlive() {
|
81
|
+
if (!this._docker) {
|
82
|
+
try {
|
83
|
+
await this.initialize();
|
84
|
+
}
|
85
|
+
catch (e) {
|
86
|
+
this._alive = false;
|
87
|
+
}
|
88
|
+
return this._alive;
|
89
|
+
}
|
90
|
+
try {
|
91
|
+
await this._docker.ping();
|
92
|
+
this._alive = true;
|
93
|
+
}
|
94
|
+
catch (e) {
|
95
|
+
this._alive = false;
|
96
|
+
}
|
97
|
+
return this._alive;
|
98
|
+
}
|
80
99
|
isAlive() {
|
81
100
|
return this._alive;
|
82
101
|
}
|
@@ -14,10 +14,12 @@ class SocketManager {
|
|
14
14
|
return this;
|
15
15
|
}
|
16
16
|
setIo(io) {
|
17
|
-
console.log('Socket server ready');
|
18
17
|
this._io = io;
|
19
18
|
this._bindIO();
|
20
19
|
}
|
20
|
+
isAlive() {
|
21
|
+
return !!this._io;
|
22
|
+
}
|
21
23
|
get io() {
|
22
24
|
if (!this._io) {
|
23
25
|
throw new Error('Socket server not ready');
|
@@ -37,11 +39,9 @@ class SocketManager {
|
|
37
39
|
_bindSocket(socket) {
|
38
40
|
socket.on('disconnect', () => this._handleSocketDestroyed(socket));
|
39
41
|
socket.on('join', (id) => {
|
40
|
-
console.log('socket joined ', id);
|
41
42
|
socket.join(id);
|
42
43
|
});
|
43
44
|
socket.on('leave', (id) => {
|
44
|
-
console.log('socket left ', id);
|
45
45
|
socket.leave(id);
|
46
46
|
});
|
47
47
|
}
|
package/dist/cjs/start.js
CHANGED
package/dist/esm/index.d.ts
CHANGED
@@ -12,6 +12,9 @@ export type StartResult = {
|
|
12
12
|
declare const _default: {
|
13
13
|
isRunning: () => boolean;
|
14
14
|
getCurrentPort: () => number | undefined;
|
15
|
+
ping: (host: string, port: number) => Promise<{
|
16
|
+
ok: boolean;
|
17
|
+
}>;
|
15
18
|
/**
|
16
19
|
* Starts the local cluster service.
|
17
20
|
* resolves when listening is done with port number. Rejects if listening failed.
|
package/dist/esm/index.js
CHANGED
@@ -16,6 +16,7 @@ import AssetsRoutes from './src/assets/routes';
|
|
16
16
|
import ProviderRoutes from './src/providers/routes';
|
17
17
|
import AttachmentRoutes from './src/attachments/routes';
|
18
18
|
import { getBindHost } from './src/utils/utils';
|
19
|
+
import request from "request";
|
19
20
|
let currentServer = null;
|
20
21
|
function createServer() {
|
21
22
|
const app = express();
|
@@ -28,6 +29,18 @@ function createServer() {
|
|
28
29
|
app.use('/assets', AssetsRoutes);
|
29
30
|
app.use('/providers', ProviderRoutes);
|
30
31
|
app.use('/attachments', AttachmentRoutes);
|
32
|
+
app.get('/status', async (req, res) => {
|
33
|
+
res.send({
|
34
|
+
ok: true,
|
35
|
+
dockerStatus: await containerManager.checkAlive(),
|
36
|
+
socketStatus: socketManager.isAlive()
|
37
|
+
});
|
38
|
+
});
|
39
|
+
app.get('/ping', async (req, res) => {
|
40
|
+
res.send({
|
41
|
+
ok: true
|
42
|
+
});
|
43
|
+
});
|
31
44
|
app.use('/', (req, res) => {
|
32
45
|
console.error('Invalid request: %s %s', req.method, req.originalUrl);
|
33
46
|
res.status(400).send({
|
@@ -56,6 +69,17 @@ export default {
|
|
56
69
|
}
|
57
70
|
return currentServer.port;
|
58
71
|
},
|
72
|
+
ping: async function (host, port) {
|
73
|
+
return new Promise((resolve, reject) => {
|
74
|
+
request.get(`http://${host}:${port}/ping`, (err, res, body) => {
|
75
|
+
if (err) {
|
76
|
+
reject(err);
|
77
|
+
return;
|
78
|
+
}
|
79
|
+
resolve(JSON.parse(body));
|
80
|
+
});
|
81
|
+
});
|
82
|
+
},
|
59
83
|
/**
|
60
84
|
* Starts the local cluster service.
|
61
85
|
* resolves when listening is done with port number. Rejects if listening failed.
|
@@ -78,6 +102,16 @@ export default {
|
|
78
102
|
if (clusterHost) {
|
79
103
|
clusterService.setClusterServiceHost(clusterHost);
|
80
104
|
}
|
105
|
+
let pingResult = undefined;
|
106
|
+
try {
|
107
|
+
pingResult = await this.ping(clusterHost, clusterPort);
|
108
|
+
}
|
109
|
+
catch (e) {
|
110
|
+
//Ignore - expected to not be running since we're starting it
|
111
|
+
}
|
112
|
+
if (pingResult?.ok) {
|
113
|
+
throw new Error(`Cluster service already running on: ${clusterHost}:${clusterPort}.`);
|
114
|
+
}
|
81
115
|
await clusterService.init();
|
82
116
|
currentServer = createServer();
|
83
117
|
const port = clusterService.getClusterServicePort();
|
@@ -36,7 +36,6 @@ router.put('/:handle/:name', async (req, res) => {
|
|
36
36
|
try {
|
37
37
|
const { handle, name } = req.params;
|
38
38
|
const url = `${endpoint}/${handle}/${name}/attachments`;
|
39
|
-
console.log('Sending PUT', url);
|
40
39
|
const result = await api.send({
|
41
40
|
method: 'PUT',
|
42
41
|
url,
|
@@ -48,7 +47,6 @@ router.put('/:handle/:name', async (req, res) => {
|
|
48
47
|
},
|
49
48
|
body: req
|
50
49
|
});
|
51
|
-
console.log('Got result from upload', result);
|
52
50
|
res.send(result);
|
53
51
|
}
|
54
52
|
catch (e) {
|
@@ -30,6 +30,7 @@ declare class ContainerManager {
|
|
30
30
|
private _version;
|
31
31
|
constructor();
|
32
32
|
initialize(): Promise<void>;
|
33
|
+
checkAlive(): Promise<boolean>;
|
33
34
|
isAlive(): boolean;
|
34
35
|
getMountPoint(kind: string, mountName: string): string;
|
35
36
|
createMounts(kind: string, mountOpts: StringMap): StringMap;
|
@@ -71,6 +71,25 @@ class ContainerManager {
|
|
71
71
|
}
|
72
72
|
throw new Error('Could not connect to docker daemon. Please make sure docker is running and working.');
|
73
73
|
}
|
74
|
+
async checkAlive() {
|
75
|
+
if (!this._docker) {
|
76
|
+
try {
|
77
|
+
await this.initialize();
|
78
|
+
}
|
79
|
+
catch (e) {
|
80
|
+
this._alive = false;
|
81
|
+
}
|
82
|
+
return this._alive;
|
83
|
+
}
|
84
|
+
try {
|
85
|
+
await this._docker.ping();
|
86
|
+
this._alive = true;
|
87
|
+
}
|
88
|
+
catch (e) {
|
89
|
+
this._alive = false;
|
90
|
+
}
|
91
|
+
return this._alive;
|
92
|
+
}
|
74
93
|
isAlive() {
|
75
94
|
return this._alive;
|
76
95
|
}
|
@@ -8,10 +8,12 @@ export class SocketManager {
|
|
8
8
|
return this;
|
9
9
|
}
|
10
10
|
setIo(io) {
|
11
|
-
console.log('Socket server ready');
|
12
11
|
this._io = io;
|
13
12
|
this._bindIO();
|
14
13
|
}
|
14
|
+
isAlive() {
|
15
|
+
return !!this._io;
|
16
|
+
}
|
15
17
|
get io() {
|
16
18
|
if (!this._io) {
|
17
19
|
throw new Error('Socket server not ready');
|
@@ -31,11 +33,9 @@ export class SocketManager {
|
|
31
33
|
_bindSocket(socket) {
|
32
34
|
socket.on('disconnect', () => this._handleSocketDestroyed(socket));
|
33
35
|
socket.on('join', (id) => {
|
34
|
-
console.log('socket joined ', id);
|
35
36
|
socket.join(id);
|
36
37
|
});
|
37
38
|
socket.on('leave', (id) => {
|
38
|
-
console.log('socket left ', id);
|
39
39
|
socket.leave(id);
|
40
40
|
});
|
41
41
|
}
|
package/dist/esm/start.js
CHANGED
package/index.ts
CHANGED
@@ -17,6 +17,7 @@ import AssetsRoutes from './src/assets/routes';
|
|
17
17
|
import ProviderRoutes from './src/providers/routes';
|
18
18
|
import AttachmentRoutes from './src/attachments/routes';
|
19
19
|
import { getBindHost } from './src/utils/utils';
|
20
|
+
import request from "request";
|
20
21
|
|
21
22
|
export type LocalClusterService = HTTP.Server & { host?: string; port?: number };
|
22
23
|
|
@@ -35,6 +36,20 @@ function createServer() {
|
|
35
36
|
app.use('/assets', AssetsRoutes);
|
36
37
|
app.use('/providers', ProviderRoutes);
|
37
38
|
app.use('/attachments', AttachmentRoutes);
|
39
|
+
app.get('/status', async (req, res) => {
|
40
|
+
res.send({
|
41
|
+
ok: true,
|
42
|
+
dockerStatus: await containerManager.checkAlive(),
|
43
|
+
socketStatus: socketManager.isAlive()
|
44
|
+
});
|
45
|
+
});
|
46
|
+
|
47
|
+
app.get('/ping', async (req, res) => {
|
48
|
+
res.send({
|
49
|
+
ok: true
|
50
|
+
});
|
51
|
+
});
|
52
|
+
|
38
53
|
app.use('/', (req: express.Request, res: express.Response) => {
|
39
54
|
console.error('Invalid request: %s %s', req.method, req.originalUrl);
|
40
55
|
res.status(400).send({
|
@@ -42,6 +57,7 @@ function createServer() {
|
|
42
57
|
error: 'Unknown'
|
43
58
|
});
|
44
59
|
});
|
60
|
+
|
45
61
|
const server = HTTP.createServer(app);
|
46
62
|
|
47
63
|
//socket
|
@@ -68,6 +84,19 @@ export default {
|
|
68
84
|
return currentServer.port;
|
69
85
|
},
|
70
86
|
|
87
|
+
ping: async function(host:string, port:number): Promise<{ ok:boolean }> {
|
88
|
+
return new Promise((resolve, reject) => {
|
89
|
+
request.get(`http://${host}:${port}/ping`, (err, res, body) => {
|
90
|
+
if (err) {
|
91
|
+
reject(err);
|
92
|
+
return;
|
93
|
+
}
|
94
|
+
|
95
|
+
resolve(JSON.parse(body));
|
96
|
+
})
|
97
|
+
})
|
98
|
+
},
|
99
|
+
|
71
100
|
/**
|
72
101
|
* Starts the local cluster service.
|
73
102
|
* resolves when listening is done with port number. Rejects if listening failed.
|
@@ -95,6 +124,17 @@ export default {
|
|
95
124
|
clusterService.setClusterServiceHost(clusterHost);
|
96
125
|
}
|
97
126
|
|
127
|
+
let pingResult = undefined;
|
128
|
+
try {
|
129
|
+
pingResult = await this.ping(clusterHost, clusterPort);
|
130
|
+
} catch (e: any) {
|
131
|
+
//Ignore - expected to not be running since we're starting it
|
132
|
+
}
|
133
|
+
|
134
|
+
if (pingResult?.ok) {
|
135
|
+
throw new Error(`Cluster service already running on: ${clusterHost}:${clusterPort}.`);
|
136
|
+
}
|
137
|
+
|
98
138
|
await clusterService.init();
|
99
139
|
|
100
140
|
currentServer = createServer();
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@kapeta/local-cluster-service",
|
3
|
-
"version": "0.8.
|
3
|
+
"version": "0.8.2",
|
4
4
|
"description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
|
5
5
|
"type": "commonjs",
|
6
6
|
"exports": {
|
@@ -31,7 +31,7 @@
|
|
31
31
|
},
|
32
32
|
"scripts": {
|
33
33
|
"start": "node ./dist/cjs/start.js",
|
34
|
-
"dev": "nodemon -e js,ts,json
|
34
|
+
"dev": "nodemon -e js,ts,json ./start.ts",
|
35
35
|
"test": "echo its fine",
|
36
36
|
"clean": "rm -rf ./dist",
|
37
37
|
"build:esm": "tsc --module esnext --outDir ./dist/esm && echo '{\"type\":\"module\"}' > ./dist/esm/package.json",
|
@@ -63,7 +63,6 @@
|
|
63
63
|
"request": "2.88.2",
|
64
64
|
"request-promise": "4.2.6",
|
65
65
|
"socket.io": "^4.5.2",
|
66
|
-
"ts-node": "^10.9.1",
|
67
66
|
"typescript": "^5.1.6",
|
68
67
|
"yaml": "^1.6.0"
|
69
68
|
},
|
@@ -79,7 +78,8 @@
|
|
79
78
|
"eslint": "^8.42.0",
|
80
79
|
"eslint-config-prettier": "^8.8.0",
|
81
80
|
"nodemon": "^2.0.2",
|
82
|
-
"prettier": "^2.8.8"
|
81
|
+
"prettier": "^2.8.8",
|
82
|
+
"ts-node": "^10.9.1"
|
83
83
|
},
|
84
84
|
"prettier": "@kapeta/prettier-config",
|
85
85
|
"release": {
|
@@ -46,7 +46,6 @@ router.put('/:handle/:name', async (req: Request, res: Response) => {
|
|
46
46
|
try {
|
47
47
|
const {handle, name} = req.params;
|
48
48
|
const url = `${endpoint}/${handle}/${name}/attachments`;
|
49
|
-
console.log('Sending PUT', url);
|
50
49
|
const result = await api.send<{url:string}>({
|
51
50
|
method: 'PUT',
|
52
51
|
url,
|
@@ -58,7 +57,6 @@ router.put('/:handle/:name', async (req: Request, res: Response) => {
|
|
58
57
|
},
|
59
58
|
body: req
|
60
59
|
});
|
61
|
-
console.log('Got result from upload', result);
|
62
60
|
res.send(result);
|
63
61
|
} catch (e:any) {
|
64
62
|
res.status(e.status ?? 500).send(e);
|
package/src/containerManager.ts
CHANGED
@@ -105,6 +105,26 @@ class ContainerManager {
|
|
105
105
|
throw new Error('Could not connect to docker daemon. Please make sure docker is running and working.');
|
106
106
|
}
|
107
107
|
|
108
|
+
async checkAlive() {
|
109
|
+
if (!this._docker) {
|
110
|
+
try {
|
111
|
+
await this.initialize();
|
112
|
+
} catch (e) {
|
113
|
+
this._alive = false;
|
114
|
+
}
|
115
|
+
return this._alive;
|
116
|
+
}
|
117
|
+
|
118
|
+
try {
|
119
|
+
await this._docker.ping()
|
120
|
+
this._alive = true;
|
121
|
+
} catch (e) {
|
122
|
+
this._alive = false;
|
123
|
+
}
|
124
|
+
|
125
|
+
return this._alive;
|
126
|
+
}
|
127
|
+
|
108
128
|
isAlive() {
|
109
129
|
return this._alive;
|
110
130
|
}
|
package/src/socketManager.ts
CHANGED
@@ -12,12 +12,14 @@ export class SocketManager {
|
|
12
12
|
}
|
13
13
|
|
14
14
|
setIo(io: Server) {
|
15
|
-
console.log('Socket server ready');
|
16
15
|
this._io = io;
|
17
|
-
|
18
16
|
this._bindIO();
|
19
17
|
}
|
20
18
|
|
19
|
+
isAlive() {
|
20
|
+
return !!this._io;
|
21
|
+
}
|
22
|
+
|
21
23
|
private get io() {
|
22
24
|
if (!this._io) {
|
23
25
|
throw new Error('Socket server not ready');
|
@@ -41,11 +43,9 @@ export class SocketManager {
|
|
41
43
|
_bindSocket(socket: Socket) {
|
42
44
|
socket.on('disconnect', () => this._handleSocketDestroyed(socket));
|
43
45
|
socket.on('join', (id) => {
|
44
|
-
console.log('socket joined ', id);
|
45
46
|
socket.join(id);
|
46
47
|
});
|
47
48
|
socket.on('leave', (id) => {
|
48
|
-
console.log('socket left ', id);
|
49
49
|
socket.leave(id);
|
50
50
|
});
|
51
51
|
}
|