mirror of
https://github.com/MCSManager/MCSManager.git
synced 2024-11-21 03:12:10 +08:00
Merge branch 'master' into master
This commit is contained in:
commit
5943039c42
@ -16,8 +16,8 @@
|
||||
|
||||
[Official Website](http://mcsmanager.com/) | [Docs](https://docs.mcsmanager.com/) | [Discord](https://discord.gg/BNpYMVX7Cd)
|
||||
|
||||
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md)
|
||||
|
||||
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md) |
|
||||
[日本語](README_JP.md)
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
@ -16,7 +16,8 @@
|
||||
|
||||
[Website Oficial](http://mcsmanager.com/) | [Documentação](https://docs.mcsmanager.com/) | [Discord](https://discord.gg/BNpYMVX7Cd)
|
||||
|
||||
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md)
|
||||
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md) |
|
||||
[日本語](README_JP.md)
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -16,7 +16,8 @@
|
||||
|
||||
[官方網站](http://mcsmanager.com/) | [教學說明](https://docs.mcsmanager.com/#/zh-cn/) | [TG 群組](https://t.me/MCSManager_dev) | [成為贊助者](https://afdian.net/a/mcsmanager)
|
||||
|
||||
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md)
|
||||
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md) |
|
||||
[日本語](README_JP.md)
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -16,7 +16,8 @@
|
||||
|
||||
[官方网站](http://mcsmanager.com/) | [使用文档](https://docs.mcsmanager.com/#/zh-cn/) | [QQ 群](https://jq.qq.com/?_wv=1027&k=Pgl9ScGw) | [TG 群](https://t.me/MCSManager_dev) | [成为赞助者](https://afdian.net/a/mcsmanager)
|
||||
|
||||
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md)
|
||||
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md) |
|
||||
[日本語](README_JP.md)
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -1905,5 +1905,10 @@
|
||||
"TXT_CODE_99ca8563": "Overwriting File",
|
||||
"TXT_CODE_ec99ddaa": "File",
|
||||
"TXT_CODE_8bd1f5d2": "is already exists in this folder, should overwrite it?",
|
||||
"TXT_CODE_8b14426e": "File already exists in this folder, Upload skipped"
|
||||
"TXT_CODE_8b14426e": "File already exists in this folder, Upload skipped",
|
||||
"TXT_CODE_bed32084": "The remote node is offline, please contact the administrator to check the online status of the panel!",
|
||||
"TXT_CODE_728fdabf": "Failed to create the instance, please try again later",
|
||||
"TXT_CODE_348c9098": "Failed to obtain the details of the specified instance, please try again!",
|
||||
"TXT_CODE_4aaec75c": "Wrong request type, please try again!",
|
||||
"TXT_CODE_903b6c50": "User does not exist, please try again"
|
||||
}
|
||||
|
@ -1905,5 +1905,10 @@
|
||||
"TXT_CODE_99ca8563": "覆盖文件",
|
||||
"TXT_CODE_ec99ddaa": "您上传的文件",
|
||||
"TXT_CODE_8bd1f5d2": "已经在目录中存在, 是否覆盖原文件?",
|
||||
"TXT_CODE_8b14426e": "文件已存在, 跳过上传"
|
||||
"TXT_CODE_8b14426e": "文件已存在, 跳过上传",
|
||||
"TXT_CODE_bed32084": "远程节点处于离线状态,请联系管理员检查面板在线状态!",
|
||||
"TXT_CODE_728fdabf": "创建实例失败,请稍后重试",
|
||||
"TXT_CODE_348c9098": "获取指定实例详情失败,请重试!",
|
||||
"TXT_CODE_4aaec75c": "请求类型错误,请重试!",
|
||||
"TXT_CODE_903b6c50": "用户不存在,请重试"
|
||||
}
|
||||
|
23
panel/package-lock.json
generated
23
panel/package-lock.json
generated
@ -25,6 +25,7 @@
|
||||
"log4js": "^6.4.0",
|
||||
"md5": "^2.3.0",
|
||||
"module-alias": "^2.2.3",
|
||||
"nanoid": "^5.0.7",
|
||||
"node-schedule": "^2.0.0",
|
||||
"open": "^8.4.0",
|
||||
"os-utils": "0.0.14",
|
||||
@ -3268,6 +3269,23 @@
|
||||
"mustache": "bin/mustache"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz",
|
||||
"integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18 || >=20"
|
||||
}
|
||||
},
|
||||
"node_modules/natural-compare": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||
@ -7355,6 +7373,11 @@
|
||||
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
|
||||
"integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz",
|
||||
"integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ=="
|
||||
},
|
||||
"natural-compare": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||
|
@ -34,6 +34,7 @@
|
||||
"log4js": "^6.4.0",
|
||||
"md5": "^2.3.0",
|
||||
"module-alias": "^2.2.3",
|
||||
"nanoid": "^5.0.7",
|
||||
"node-schedule": "^2.0.0",
|
||||
"open": "^8.4.0",
|
||||
"os-utils": "0.0.14",
|
||||
|
@ -1,39 +1,27 @@
|
||||
// Define subsystem loading and routing loading for the application
|
||||
|
||||
import Koa from "koa";
|
||||
import Router from "@koa/router";
|
||||
|
||||
// Load subsystem
|
||||
import "./service/user_service";
|
||||
import "./service/visual_data";
|
||||
import "./service/remote_service";
|
||||
import "./service/user_statistics";
|
||||
|
||||
// Load routes
|
||||
import overviewRouter from "./routers/overview_router";
|
||||
|
||||
import userRouter from "./routers/user_overview_router";
|
||||
import loginRouter from "./routers/login_router";
|
||||
import lowUserRouter from "./routers/general_user_router";
|
||||
|
||||
import settingsRouter from "./routers/settings_router";
|
||||
|
||||
import instanceRouter from "./routers/instance_admin_router";
|
||||
import userInstanceRouter from "./routers/instance_operate_router";
|
||||
|
||||
import serviceRouter from "./routers/daemon_router";
|
||||
import filemanager_router from "./routers/filemananger_router";
|
||||
|
||||
import businessInstanceRouter from "./routers/business_instance_router";
|
||||
import businessUserRouter from "./routers/manage_user_router";
|
||||
|
||||
import scheduleRouter from "./routers/schedule_router";
|
||||
|
||||
import environmentRouter from "./routers/environment_router";
|
||||
import exchangeRouter from "./routers/instance_exchange_router";
|
||||
|
||||
// all routes load entry points
|
||||
export function mountRouters(app: Koa<Koa.DefaultState, Koa.DefaultContext>) {
|
||||
// API router
|
||||
const apiRouter = new Router({ prefix: "/api" });
|
||||
apiRouter.use(overviewRouter.routes()).use(overviewRouter.allowedMethods());
|
||||
apiRouter.use(userInstanceRouter.routes()).use(userInstanceRouter.allowedMethods());
|
||||
@ -48,7 +36,7 @@ export function mountRouters(app: Koa<Koa.DefaultState, Koa.DefaultContext>) {
|
||||
apiRouter.use(scheduleRouter.routes()).use(scheduleRouter.allowedMethods());
|
||||
apiRouter.use(settingsRouter.routes()).use(settingsRouter.allowedMethods());
|
||||
apiRouter.use(environmentRouter.routes()).use(environmentRouter.allowedMethods());
|
||||
apiRouter.use(exchangeRouter.routes()).use(exchangeRouter.allowedMethods());
|
||||
|
||||
// Top router
|
||||
app.use(apiRouter.routes()).use(apiRouter.allowedMethods());
|
||||
}
|
||||
|
@ -4,13 +4,13 @@ import permission from "../middleware/permission";
|
||||
import { bind2FA, confirm2FaQRCode, getUserUuid, logout } from "../service/passport_service";
|
||||
import userSystem from "../service/user_service";
|
||||
import { getToken, isAjax } from "../service/passport_service";
|
||||
import RemoteServiceSubsystem from "../service/remote_service";
|
||||
import RemoteRequest from "../service/remote_command";
|
||||
import { isTopPermissionByUuid } from "../service/permission_service";
|
||||
import validator from "../middleware/validator";
|
||||
import { v4 } from "uuid";
|
||||
import { $t } from "../i18n";
|
||||
import { ROLE } from "../entity/user";
|
||||
import { getInstancesByUuid } from "../service/instance_service";
|
||||
import { toBoolean } from "common";
|
||||
|
||||
const router = new Router({ prefix: "/auth" });
|
||||
|
||||
@ -36,79 +36,15 @@ router.get("/", permission({ level: ROLE.USER, token: false, speedLimit: false }
|
||||
let uuid = getUserUuid(ctx);
|
||||
// The front end can choose to require advanced data
|
||||
const advanced = ctx.query.advanced;
|
||||
|
||||
// Admin permissions can be obtained from anyone
|
||||
if (isTopPermissionByUuid(uuid)) {
|
||||
if (ctx.query.uuid) uuid = String(ctx.query.uuid);
|
||||
}
|
||||
if (isTopPermissionByUuid(uuid) && ctx.query.uuid) uuid = String(ctx.query.uuid);
|
||||
|
||||
// Some and only Ajax requests grant access
|
||||
if (isAjax(ctx)) {
|
||||
const user = userSystem.getInstance(uuid);
|
||||
if (!user) throw new Error("The UID does not exist");
|
||||
|
||||
// Advanced functions are optional, analyze each instance data
|
||||
let resInstances = [];
|
||||
if (advanced) {
|
||||
const instances = user.instances;
|
||||
for (const iterator of instances) {
|
||||
const remoteService = RemoteServiceSubsystem.getInstance(iterator.daemonId);
|
||||
// If the remote service doesn't exist at all, load a deleted prompt
|
||||
if (!remoteService) {
|
||||
resInstances.push({
|
||||
hostIp: "-- Unknown --",
|
||||
instanceUuid: iterator.instanceUuid,
|
||||
daemonId: iterator.daemonId,
|
||||
status: -1,
|
||||
nickname: "--",
|
||||
remarks: "--"
|
||||
});
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
// Note: UUID can be integrated here to save the returned traffic, and this optimization will not be done for the time being
|
||||
let instancesInfo = await new RemoteRequest(remoteService).request("instance/section", {
|
||||
instanceUuids: [iterator.instanceUuid]
|
||||
});
|
||||
instancesInfo = instancesInfo[0];
|
||||
resInstances.push({
|
||||
hostIp: `${remoteService.config.ip}:${remoteService.config.port}`,
|
||||
remarks: remoteService.config.remarks,
|
||||
instanceUuid: instancesInfo.instanceUuid,
|
||||
daemonId: remoteService.uuid,
|
||||
status: instancesInfo.status,
|
||||
nickname: instancesInfo.config.nickname,
|
||||
ie: instancesInfo.config.ie,
|
||||
oe: instancesInfo.config.oe,
|
||||
endTime: instancesInfo.config.endTime,
|
||||
lastDatetime: instancesInfo.config.lastDatetime,
|
||||
stopCommand: instancesInfo.config.stopCommand
|
||||
});
|
||||
} catch (error: any) {
|
||||
resInstances.push({
|
||||
hostIp: `${remoteService.config.ip}:${remoteService.config.port}`,
|
||||
instanceUuid: iterator.instanceUuid,
|
||||
daemonId: iterator.daemonId,
|
||||
status: -1,
|
||||
nickname: "--"
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resInstances = user.instances;
|
||||
}
|
||||
// respond to user data
|
||||
ctx.body = {
|
||||
uuid: user.uuid,
|
||||
userName: user.userName,
|
||||
loginTime: user.loginTime,
|
||||
registerTime: user.registerTime,
|
||||
instances: resInstances,
|
||||
permission: user.permission,
|
||||
token: getToken(ctx),
|
||||
apiKey: user.apiKey,
|
||||
isInit: user.isInit,
|
||||
open2FA: user.open2FA,
|
||||
secret: user.secret
|
||||
};
|
||||
const res = await getInstancesByUuid(uuid, toBoolean(advanced) || false);
|
||||
res.token = getToken(ctx);
|
||||
ctx.body = res;
|
||||
}
|
||||
});
|
||||
|
||||
|
38
panel/src/app/routers/instance_exchange_router.ts
Executable file
38
panel/src/app/routers/instance_exchange_router.ts
Executable file
@ -0,0 +1,38 @@
|
||||
import Router from "@koa/router";
|
||||
import permission from "../middleware/permission";
|
||||
import validator from "../middleware/validator";
|
||||
import { ROLE } from "../entity/user";
|
||||
import {
|
||||
buyOrRenewInstance,
|
||||
queryInstanceByUserId,
|
||||
RequestAction
|
||||
} from "../service/exchange_service";
|
||||
|
||||
const router = new Router({ prefix: "/exchange" });
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
permission({ level: ROLE.ADMIN }),
|
||||
validator({
|
||||
body: {
|
||||
request_action: String
|
||||
}
|
||||
}),
|
||||
async (ctx) => {
|
||||
try {
|
||||
const requestAction = ctx.request.body.request_action;
|
||||
const params = ctx.request.body.data ?? {};
|
||||
if ([RequestAction.RENEW, RequestAction.BUY].includes(requestAction)) {
|
||||
ctx.body = await buyOrRenewInstance(requestAction, params);
|
||||
return;
|
||||
}
|
||||
if (requestAction === RequestAction.QUERY_INSTANCE) {
|
||||
ctx.body = await queryInstanceByUserId(params);
|
||||
}
|
||||
} catch (err) {
|
||||
ctx.body = err;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
97
panel/src/app/service/exchange_service.ts
Normal file
97
panel/src/app/service/exchange_service.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import RemoteServiceSubsystem from "../service/remote_service";
|
||||
import RemoteRequest from "../service/remote_command";
|
||||
import user_service from "../service/user_service";
|
||||
import { customAlphabet } from "nanoid";
|
||||
import { t } from "i18next";
|
||||
import { toNumber, toText } from "common";
|
||||
import { getInstancesByUuid } from "./instance_service";
|
||||
|
||||
const getNanoId = customAlphabet(
|
||||
"1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
||||
6
|
||||
);
|
||||
|
||||
export enum RequestAction {
|
||||
BUY = "buy",
|
||||
RENEW = "sell",
|
||||
QUERY_INSTANCE = "query_instance"
|
||||
}
|
||||
|
||||
export async function buyOrRenewInstance(
|
||||
request_action: RequestAction,
|
||||
params: Record<string, any>
|
||||
) {
|
||||
const node_id = toText(params.node_id) ?? "";
|
||||
const instance_id = toText(params.instance_id) ?? "";
|
||||
const username = toText(params.username) ?? "";
|
||||
const hours = toNumber(params.hours) ?? 0;
|
||||
const payload = params.payload ?? {};
|
||||
|
||||
const remoteService = RemoteServiceSubsystem.getInstance(node_id || "");
|
||||
if (!remoteService?.available) {
|
||||
throw new Error(t("TXT_CODE_bed32084"));
|
||||
}
|
||||
|
||||
const { request: remoteRequest } = new RemoteRequest(remoteService);
|
||||
|
||||
if (request_action === RequestAction.BUY) {
|
||||
payload.endTime = (payload.endTime ? payload.endTime : Date.now()) + hours * 3600 * 1000;
|
||||
const { instanceUuid: newInstanceId, config: newInstanceConfig } = await remoteRequest(
|
||||
"instance/new",
|
||||
payload
|
||||
);
|
||||
if (!newInstanceId) throw new Error(t("TXT_CODE_728fdabf"));
|
||||
const newPassword = getNanoId(12);
|
||||
const newUser = await user_service.create({
|
||||
userName: username + "-" + getNanoId(6),
|
||||
passWord: newPassword,
|
||||
permission: 1,
|
||||
instances: [
|
||||
{
|
||||
instanceUuid: newInstanceId,
|
||||
daemonId: node_id
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return {
|
||||
instance_id: newInstanceId,
|
||||
instance_config: newInstanceConfig,
|
||||
username: newUser.userName,
|
||||
password: newPassword,
|
||||
uuid: newUser.uuid,
|
||||
expire: toNumber(newInstanceConfig.endTime)
|
||||
};
|
||||
}
|
||||
|
||||
if (request_action === RequestAction.RENEW) {
|
||||
const instanceInfo = await remoteRequest("instance/detail", {
|
||||
instanceUuid: instance_id
|
||||
});
|
||||
if (!instanceInfo.config) throw new Error(t("TXT_CODE_348c9098"));
|
||||
instanceInfo.config.endTime =
|
||||
(instanceInfo.config?.endTime ? instanceInfo.config.endTime : Date.now()) +
|
||||
hours * 3600 * 1000;
|
||||
await remoteRequest("instance/update", {
|
||||
instanceUuid: instance_id,
|
||||
config: instanceInfo.config
|
||||
});
|
||||
return {
|
||||
instance_id,
|
||||
instance_config: instanceInfo.config,
|
||||
expire: toNumber(instanceInfo.config.endTime),
|
||||
username: "",
|
||||
password: "",
|
||||
uuid: ""
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(t("TXT_CODE_4aaec75c"));
|
||||
}
|
||||
|
||||
export async function queryInstanceByUserId(params: Record<string, any>) {
|
||||
const uuid = toText(params.uuid) || "";
|
||||
const user = user_service.getInstance(uuid);
|
||||
if (!user) throw new Error(t("TXT_CODE_903b6c50"));
|
||||
return await getInstancesByUuid(uuid, false);
|
||||
}
|
@ -1,3 +1,7 @@
|
||||
import userSystem from "../service/user_service";
|
||||
import RemoteServiceSubsystem from "../service/remote_service";
|
||||
import RemoteRequest from "../service/remote_command";
|
||||
|
||||
// Multi-forward operation method
|
||||
export function multiOperationForwarding(
|
||||
instances: any[],
|
||||
@ -22,3 +26,73 @@ export function multiOperationForwarding(
|
||||
callback(daemonId, instanceUuids);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getInstancesByUuid(uuid: string, advanced: boolean = false) {
|
||||
const user = userSystem.getInstance(uuid);
|
||||
if (!user) throw new Error("The UID does not exist");
|
||||
|
||||
// Advanced functions are optional, analyze each instance data
|
||||
let resInstances = [];
|
||||
if (advanced) {
|
||||
const instances = user.instances;
|
||||
for (const iterator of instances) {
|
||||
const remoteService = RemoteServiceSubsystem.getInstance(iterator.daemonId);
|
||||
// If the remote service doesn't exist at all, load a deleted prompt
|
||||
if (!remoteService) {
|
||||
resInstances.push({
|
||||
hostIp: "-- Unknown --",
|
||||
instanceUuid: iterator.instanceUuid,
|
||||
daemonId: iterator.daemonId,
|
||||
status: -1,
|
||||
nickname: "--",
|
||||
remarks: "--"
|
||||
});
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
// Note: UUID can be integrated here to save the returned traffic, and this optimization will not be done for the time being
|
||||
let instancesInfo = await new RemoteRequest(remoteService).request("instance/section", {
|
||||
instanceUuids: [iterator.instanceUuid]
|
||||
});
|
||||
instancesInfo = instancesInfo[0];
|
||||
resInstances.push({
|
||||
hostIp: `${remoteService.config.ip}:${remoteService.config.port}`,
|
||||
remarks: remoteService.config.remarks,
|
||||
instanceUuid: instancesInfo.instanceUuid,
|
||||
daemonId: remoteService.uuid,
|
||||
status: instancesInfo.status,
|
||||
nickname: instancesInfo.config.nickname,
|
||||
ie: instancesInfo.config.ie,
|
||||
oe: instancesInfo.config.oe,
|
||||
endTime: instancesInfo.config.endTime,
|
||||
lastDatetime: instancesInfo.config.lastDatetime,
|
||||
stopCommand: instancesInfo.config.stopCommand
|
||||
});
|
||||
} catch (error: any) {
|
||||
resInstances.push({
|
||||
hostIp: `${remoteService.config.ip}:${remoteService.config.port}`,
|
||||
instanceUuid: iterator.instanceUuid,
|
||||
daemonId: iterator.daemonId,
|
||||
status: -1,
|
||||
nickname: "--"
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resInstances = user.instances;
|
||||
}
|
||||
// respond to user data
|
||||
return {
|
||||
uuid: user.uuid,
|
||||
userName: user.userName,
|
||||
loginTime: user.loginTime,
|
||||
registerTime: user.registerTime,
|
||||
instances: resInstances,
|
||||
permission: user.permission,
|
||||
apiKey: user.apiKey,
|
||||
isInit: user.isInit,
|
||||
open2FA: user.open2FA,
|
||||
secret: user.secret,
|
||||
token: ""
|
||||
};
|
||||
}
|
||||
|
@ -23,7 +23,12 @@ export default class RemoteRequest {
|
||||
}
|
||||
|
||||
// request to remote daemon
|
||||
public async request(event: string, data?: any, timeout = 6000, force = false): Promise<any> {
|
||||
public async request<T = any>(
|
||||
event: string,
|
||||
data?: any,
|
||||
timeout = 6000,
|
||||
force = false
|
||||
): Promise<T> {
|
||||
if (!this.rService || !this.rService.socket) throw new Error($t("TXT_CODE_3d94ea16"));
|
||||
if (!this.rService.available && !force)
|
||||
throw new Error($t("TXT_CODE_b7d38e78") + ` IP: ${this.rService.config.ip}`);
|
||||
|
Loading…
Reference in New Issue
Block a user