[FEATURE] Quality of Life Improvements for Management (#7)
Co-authored-by: Dunemask <dunemask@gmail.com> Reviewed-on: https://gitea.dunemask.dev/elysium/minecluster/pulls/7
This commit is contained in:
parent
6eb4ed3e95
commit
23efaafe1d
18 changed files with 479 additions and 39 deletions
|
@ -8,8 +8,9 @@ RUN npm i
|
||||||
COPY public public
|
COPY public public
|
||||||
COPY dist dist
|
COPY dist dist
|
||||||
COPY src src
|
COPY src src
|
||||||
COPY lib lib
|
|
||||||
COPY index.html .
|
COPY index.html .
|
||||||
COPY vite.config.js .
|
COPY vite.config.js .
|
||||||
RUN npm run build:react
|
RUN npm run build:react
|
||||||
|
# Copy Backend resources over
|
||||||
|
COPY lib lib
|
||||||
CMD ["npm","start"]
|
CMD ["npm","start"]
|
||||||
|
|
|
@ -15,7 +15,7 @@ const dnsRegex = new RegExp(
|
||||||
function payloadFilter(req, res) {
|
function payloadFilter(req, res) {
|
||||||
const serverSpec = req.body;
|
const serverSpec = req.body;
|
||||||
if (!serverSpec) return res.sendStatus(400);
|
if (!serverSpec) return res.sendStatus(400);
|
||||||
const { name, host, version, serverType, memory } = serverSpec;
|
const { name, host, version, serverType, memory, extraPorts } = serverSpec;
|
||||||
const { backupHost, backupBucket, backupId, backupKey, backupInterval } =
|
const { backupHost, backupBucket, backupId, backupKey, backupInterval } =
|
||||||
serverSpec;
|
serverSpec;
|
||||||
if (!name) return res.status(400).send("Server name is required!");
|
if (!name) return res.status(400).send("Server name is required!");
|
||||||
|
@ -24,6 +24,14 @@ function payloadFilter(req, res) {
|
||||||
if (!version) return res.status(400).send("Server version is required!");
|
if (!version) return res.status(400).send("Server version is required!");
|
||||||
if (!serverType) return res.status(400).send("Server type is required!");
|
if (!serverType) return res.status(400).send("Server type is required!");
|
||||||
if (!memory) return res.status(400).send("Memory is required!");
|
if (!memory) return res.status(400).send("Memory is required!");
|
||||||
|
if (
|
||||||
|
!!extraPorts &&
|
||||||
|
(!Array.isArray(extraPorts) ||
|
||||||
|
extraPorts.find((e) => typeof e !== "string" || e.length > 5))
|
||||||
|
)
|
||||||
|
return res
|
||||||
|
.status(400)
|
||||||
|
.send("Extra ports must be a list of strings with length of 5!");
|
||||||
// TODO: Impliment non creation time backups
|
// TODO: Impliment non creation time backups
|
||||||
if (
|
if (
|
||||||
!!backupHost ||
|
!!backupHost ||
|
||||||
|
|
|
@ -13,6 +13,7 @@ CREATE TABLE servers (
|
||||||
backup_id varchar(255) DEFAULT NULL,
|
backup_id varchar(255) DEFAULT NULL,
|
||||||
backup_key varchar(255) DEFAULT NULL,
|
backup_key varchar(255) DEFAULT NULL,
|
||||||
backup_interval varchar(255) DEFAULT NULL,
|
backup_interval varchar(255) DEFAULT NULL,
|
||||||
|
extra_ports varchar(7)[] DEFAULT NULL,
|
||||||
CONSTRAINT unique_host UNIQUE(host)
|
CONSTRAINT unique_host UNIQUE(host)
|
||||||
);
|
);
|
||||||
ALTER SEQUENCE servers_id_seq OWNED BY servers.id;
|
ALTER SEQUENCE servers_id_seq OWNED BY servers.id;
|
|
@ -16,6 +16,7 @@ export async function createServerEntry(serverSpec) {
|
||||||
version,
|
version,
|
||||||
serverType: server_type,
|
serverType: server_type,
|
||||||
memory,
|
memory,
|
||||||
|
extraPorts: extra_ports,
|
||||||
backupHost: backup_host,
|
backupHost: backup_host,
|
||||||
backupBucket: backup_bucket_path,
|
backupBucket: backup_bucket_path,
|
||||||
backupId: backup_id,
|
backupId: backup_id,
|
||||||
|
@ -28,6 +29,7 @@ export async function createServerEntry(serverSpec) {
|
||||||
version,
|
version,
|
||||||
server_type,
|
server_type,
|
||||||
memory,
|
memory,
|
||||||
|
extra_ports,
|
||||||
backup_enabled: !!backup_interval, // We already verified the payload, so any backup key will work
|
backup_enabled: !!backup_interval, // We already verified the payload, so any backup key will work
|
||||||
backup_host,
|
backup_host,
|
||||||
backup_bucket_path,
|
backup_bucket_path,
|
||||||
|
@ -45,6 +47,7 @@ export async function createServerEntry(serverSpec) {
|
||||||
version,
|
version,
|
||||||
server_type: serverType,
|
server_type: serverType,
|
||||||
memory,
|
memory,
|
||||||
|
extra_ports: extraPorts,
|
||||||
backup_enabled: backupEnabled,
|
backup_enabled: backupEnabled,
|
||||||
backup_host: backupHost,
|
backup_host: backupHost,
|
||||||
backup_bucket_path: backupPath,
|
backup_bucket_path: backupPath,
|
||||||
|
@ -61,6 +64,7 @@ export async function createServerEntry(serverSpec) {
|
||||||
version,
|
version,
|
||||||
serverType,
|
serverType,
|
||||||
memory,
|
memory,
|
||||||
|
extraPorts,
|
||||||
backupEnabled,
|
backupEnabled,
|
||||||
backupHost,
|
backupHost,
|
||||||
backupPath,
|
backupPath,
|
||||||
|
@ -94,6 +98,7 @@ export async function getServerEntry(serverId) {
|
||||||
version,
|
version,
|
||||||
server_type: serverType,
|
server_type: serverType,
|
||||||
memory,
|
memory,
|
||||||
|
extra_ports: extraPorts,
|
||||||
backup_enabled: backupEnabled,
|
backup_enabled: backupEnabled,
|
||||||
backup_host: backupHost,
|
backup_host: backupHost,
|
||||||
backup_bucket_path: backupPath,
|
backup_bucket_path: backupPath,
|
||||||
|
@ -110,6 +115,7 @@ export async function getServerEntry(serverId) {
|
||||||
version,
|
version,
|
||||||
serverType,
|
serverType,
|
||||||
memory,
|
memory,
|
||||||
|
extraPorts,
|
||||||
backupEnabled,
|
backupEnabled,
|
||||||
backupHost,
|
backupHost,
|
||||||
backupPath,
|
backupPath,
|
||||||
|
|
23
lib/k8s/configs/extra-svc.yml
Normal file
23
lib/k8s/configs/extra-svc.yml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
minecluster.dunemask.net/id: changeme-server-id
|
||||||
|
labels:
|
||||||
|
app: changeme-app
|
||||||
|
name: changeme-extra
|
||||||
|
namespace: changeme-namespace
|
||||||
|
spec:
|
||||||
|
internalTrafficPolicy: Cluster
|
||||||
|
ipFamilies:
|
||||||
|
- IPv4
|
||||||
|
ipFamilyPolicy: SingleStack
|
||||||
|
# ports: Programatically generated
|
||||||
|
# - name: port-name
|
||||||
|
# port: 1234
|
||||||
|
# protocol: TCP
|
||||||
|
# targetPort: port-name
|
||||||
|
selector:
|
||||||
|
app: changeme-app
|
||||||
|
sessionAffinity: None
|
||||||
|
type: ClusterIP
|
|
@ -78,6 +78,7 @@ export function getServerAssets(serverId) {
|
||||||
backupSecret: secrets.find((s) =>
|
backupSecret: secrets.find((s) =>
|
||||||
s.metadata.name.endsWith("-backup-secret"),
|
s.metadata.name.endsWith("-backup-secret"),
|
||||||
),
|
),
|
||||||
|
extraService: services.find((s) => s.metadata.name.endsWith("-extra")),
|
||||||
};
|
};
|
||||||
for (var k in serverAssets) if (serverAssets[k]) return serverAssets;
|
for (var k in serverAssets) if (serverAssets[k]) return serverAssets;
|
||||||
// If no assets exist, return nothing
|
// If no assets exist, return nothing
|
||||||
|
|
|
@ -19,6 +19,37 @@ const namespace = process.env.MCL_SERVER_NAMESPACE;
|
||||||
|
|
||||||
const loadYaml = (f) => yaml.load(fs.readFileSync(path.resolve(f), "utf8"));
|
const loadYaml = (f) => yaml.load(fs.readFileSync(path.resolve(f), "utf8"));
|
||||||
|
|
||||||
|
function createExtraService(serverSpec) {
|
||||||
|
const { mclName, id, extraPorts } = serverSpec;
|
||||||
|
if (!extraPorts) return;
|
||||||
|
const serviceYaml = loadYaml("lib/k8s/configs/extra-svc.yml");
|
||||||
|
serviceYaml.metadata.labels.app = `mcl-${mclName}-app`;
|
||||||
|
serviceYaml.metadata.name = `mcl-${mclName}-extra`;
|
||||||
|
serviceYaml.metadata.namespace = namespace;
|
||||||
|
serviceYaml.metadata.annotations["minecluster.dunemask.net/id"] = id;
|
||||||
|
serviceYaml.spec.selector.app = `mcl-${mclName}-app`;
|
||||||
|
// Port List:
|
||||||
|
const portList = extraPorts.map((p) => ({
|
||||||
|
port: parseInt(p),
|
||||||
|
name: `mcl-extra-${p}`,
|
||||||
|
}));
|
||||||
|
const tcpPorts = portList.map(({ port, name }) => ({
|
||||||
|
port,
|
||||||
|
name: `${name}-tcp`,
|
||||||
|
protocol: "TCP",
|
||||||
|
targetPort: port,
|
||||||
|
}));
|
||||||
|
const udpPorts = portList.map(({ port, name }) => ({
|
||||||
|
port,
|
||||||
|
name: `${name}-udp`,
|
||||||
|
protocol: "UDP",
|
||||||
|
targetPort: port,
|
||||||
|
}));
|
||||||
|
|
||||||
|
serviceYaml.spec.ports = [...tcpPorts, ...udpPorts];
|
||||||
|
return serviceYaml;
|
||||||
|
}
|
||||||
|
|
||||||
function createBackupSecret(serverSpec) {
|
function createBackupSecret(serverSpec) {
|
||||||
if (!serverSpec.backupEnabled) return; // If backup not defined, don't create RCLONE secret
|
if (!serverSpec.backupEnabled) return; // If backup not defined, don't create RCLONE secret
|
||||||
const { mclName, id, backupId, backupKey, backupHost } = serverSpec;
|
const { mclName, id, backupId, backupKey, backupHost } = serverSpec;
|
||||||
|
@ -161,10 +192,26 @@ export default async function createServerResources(createSpec) {
|
||||||
const serverDeploy = createServerDeploy(createSpec);
|
const serverDeploy = createServerDeploy(createSpec);
|
||||||
const serverService = createServerService(createSpec);
|
const serverService = createServerService(createSpec);
|
||||||
const rconService = createRconService(createSpec);
|
const rconService = createRconService(createSpec);
|
||||||
k8sCore.createNamespacedPersistentVolumeClaim(namespace, serverVolume);
|
const extraService = createExtraService(createSpec);
|
||||||
if (!!backupSecret) k8sCore.createNamespacedSecret(namespace, backupSecret);
|
const serverResources = [];
|
||||||
k8sCore.createNamespacedSecret(namespace, rconSecret);
|
serverResources.push(
|
||||||
k8sCore.createNamespacedService(namespace, serverService);
|
k8sCore.createNamespacedPersistentVolumeClaim(namespace, serverVolume),
|
||||||
k8sCore.createNamespacedService(namespace, rconService);
|
);
|
||||||
k8sDeps.createNamespacedDeployment(namespace, serverDeploy);
|
if (!!extraService)
|
||||||
|
serverResources.push(
|
||||||
|
k8sCore.createNamespacedService(namespace, extraService),
|
||||||
|
);
|
||||||
|
if (!!backupSecret)
|
||||||
|
serverResources.push(
|
||||||
|
k8sCore.createNamespacedSecret(namespace, backupSecret),
|
||||||
|
);
|
||||||
|
serverResources.push(k8sCore.createNamespacedSecret(namespace, rconSecret));
|
||||||
|
serverResources.push(
|
||||||
|
k8sCore.createNamespacedService(namespace, serverService),
|
||||||
|
);
|
||||||
|
serverResources.push(k8sCore.createNamespacedService(namespace, rconService));
|
||||||
|
serverResources.push(
|
||||||
|
k8sDeps.createNamespacedDeployment(namespace, serverDeploy),
|
||||||
|
);
|
||||||
|
return await Promise.all(serverResources);
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,10 @@ export default async function deleteServerResources(serverSpec) {
|
||||||
const deleteBackupSecret = deleteOnExist(server.backupSecret, (name) =>
|
const deleteBackupSecret = deleteOnExist(server.backupSecret, (name) =>
|
||||||
k8sCore.deleteNamespacedSecret(name, namespace),
|
k8sCore.deleteNamespacedSecret(name, namespace),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const deleteExtraService = deleteOnExist(server.extraService, (name) =>
|
||||||
|
k8sCore.deleteNamespacedService(name, namespace),
|
||||||
|
);
|
||||||
const deleteVolume = deleteOnExist(server.volume, (name) =>
|
const deleteVolume = deleteOnExist(server.volume, (name) =>
|
||||||
k8sCore.deleteNamespacedPersistentVolumeClaim(name, namespace),
|
k8sCore.deleteNamespacedPersistentVolumeClaim(name, namespace),
|
||||||
);
|
);
|
||||||
|
@ -59,6 +63,7 @@ export default async function deleteServerResources(serverSpec) {
|
||||||
deleteService,
|
deleteService,
|
||||||
deleteRconService,
|
deleteRconService,
|
||||||
deleteRconSecret,
|
deleteRconSecret,
|
||||||
|
deleteExtraService,
|
||||||
deleteBackupSecret,
|
deleteBackupSecret,
|
||||||
deleteVolume,
|
deleteVolume,
|
||||||
]).catch(deleteError);
|
]).catch(deleteError);
|
||||||
|
|
|
@ -82,7 +82,7 @@ export async function uploadServerItem(serverSpec, file) {
|
||||||
const { path } = serverSpec;
|
const { path } = serverSpec;
|
||||||
pathSecurityCheck(path);
|
pathSecurityCheck(path);
|
||||||
await useServerFtp(serverSpec, async (c) => {
|
await useServerFtp(serverSpec, async (c) => {
|
||||||
await c.uploadFrom(fileStream, `${path}/${file.originalname}`);
|
await c.uploadFrom(fileStream, path);
|
||||||
}).catch(handleError);
|
}).catch(handleError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
284
package-lock.json
generated
284
package-lock.json
generated
|
@ -40,6 +40,7 @@
|
||||||
"prettier": "^3.1.0",
|
"prettier": "^3.1.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-quill": "^2.0.0",
|
||||||
"react-router-dom": "^6.20.1",
|
"react-router-dom": "^6.20.1",
|
||||||
"react-toastify": "^9.1.3",
|
"react-toastify": "^9.1.3",
|
||||||
"socket.io-client": "^4.7.2",
|
"socket.io-client": "^4.7.2",
|
||||||
|
@ -3463,6 +3464,15 @@
|
||||||
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
|
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/quill": {
|
||||||
|
"version": "1.3.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz",
|
||||||
|
"integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"parchment": "^1.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "17.0.73",
|
"version": "17.0.73",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.73.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.73.tgz",
|
||||||
|
@ -4375,6 +4385,15 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/clone": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/clsx": {
|
"node_modules/clsx": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
|
||||||
|
@ -4712,6 +4731,26 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/deep-equal": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"is-arguments": "^1.1.1",
|
||||||
|
"is-date-object": "^1.0.5",
|
||||||
|
"is-regex": "^1.1.4",
|
||||||
|
"object-is": "^1.1.5",
|
||||||
|
"object-keys": "^1.1.1",
|
||||||
|
"regexp.prototype.flags": "^1.5.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/deepmerge": {
|
"node_modules/deepmerge": {
|
||||||
"version": "4.3.1",
|
"version": "4.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||||
|
@ -4721,6 +4760,37 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/define-data-property": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"get-intrinsic": "^1.2.1",
|
||||||
|
"gopd": "^1.0.1",
|
||||||
|
"has-property-descriptors": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/define-properties": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"define-data-property": "^1.0.1",
|
||||||
|
"has-property-descriptors": "^1.0.0",
|
||||||
|
"object-keys": "^1.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/delayed-stream": {
|
"node_modules/delayed-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
@ -4942,6 +5012,12 @@
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eventemitter3": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/events": {
|
"node_modules/events": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
|
||||||
|
@ -5028,6 +5104,12 @@
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-diff": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/fast-json-stable-stringify": {
|
"node_modules/fast-json-stable-stringify": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||||
|
@ -5229,9 +5311,21 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/functions-have-names": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fuzzy-search": {
|
"node_modules/fuzzy-search": {
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
|
@ -5277,13 +5371,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/get-intrinsic": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
|
||||||
"integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
|
"integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.1",
|
"function-bind": "^1.1.2",
|
||||||
"has": "^1.0.3",
|
"has-proto": "^1.0.1",
|
||||||
"has-symbols": "^1.0.3"
|
"has-symbols": "^1.0.3",
|
||||||
|
"hasown": "^2.0.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
@ -5373,6 +5468,7 @@
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.1"
|
"function-bind": "^1.1.1"
|
||||||
},
|
},
|
||||||
|
@ -5389,6 +5485,29 @@
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/has-property-descriptors": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"get-intrinsic": "^1.2.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/has-symbols": {
|
"node_modules/has-symbols": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||||
|
@ -5419,6 +5538,17 @@
|
||||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||||
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
|
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/hasown": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hoist-non-react-statics": {
|
"node_modules/hoist-non-react-statics": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
@ -5633,6 +5763,21 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-date-object": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"has-tostringtag": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-extglob": {
|
"node_modules/is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
|
@ -5691,6 +5836,22 @@
|
||||||
"node": ">=0.12.0"
|
"node": ">=0.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-regex": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind": "^1.0.2",
|
||||||
|
"has-tostringtag": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-typed-array": {
|
"node_modules/is-typed-array": {
|
||||||
"version": "1.1.12",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz",
|
||||||
|
@ -6413,6 +6574,31 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/object-is": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind": "^1.0.2",
|
||||||
|
"define-properties": "^1.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/object-keys": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/object-path": {
|
"node_modules/object-path": {
|
||||||
"version": "0.11.8",
|
"version": "0.11.8",
|
||||||
"resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.8.tgz",
|
||||||
|
@ -6488,6 +6674,12 @@
|
||||||
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
|
||||||
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
|
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/parchment": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/parent-module": {
|
"node_modules/parent-module": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
|
@ -6882,6 +7074,34 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/quill": {
|
||||||
|
"version": "1.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz",
|
||||||
|
"integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"clone": "^2.1.1",
|
||||||
|
"deep-equal": "^1.0.1",
|
||||||
|
"eventemitter3": "^2.0.3",
|
||||||
|
"extend": "^3.0.2",
|
||||||
|
"parchment": "^1.1.4",
|
||||||
|
"quill-delta": "^3.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/quill-delta": {
|
||||||
|
"version": "3.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz",
|
||||||
|
"integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"deep-equal": "^1.0.1",
|
||||||
|
"extend": "^3.0.2",
|
||||||
|
"fast-diff": "1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/range-parser": {
|
"node_modules/range-parser": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
|
@ -7038,6 +7258,21 @@
|
||||||
"integrity": "sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg==",
|
"integrity": "sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/react-quill": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-quill/-/react-quill-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-4qQtv1FtCfLgoD3PXAur5RyxuUbPXQGOHgTlFie3jtxp43mXDtzCKaOgQ3mLyZfi1PUlyjycfivKelFhy13QUg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/quill": "^1.3.10",
|
||||||
|
"lodash": "^4.17.4",
|
||||||
|
"quill": "^1.3.7"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16 || ^17 || ^18",
|
||||||
|
"react-dom": "^16 || ^17 || ^18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-redux": {
|
"node_modules/react-redux": {
|
||||||
"version": "7.2.9",
|
"version": "7.2.9",
|
||||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
|
||||||
|
@ -7233,6 +7468,23 @@
|
||||||
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==",
|
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/regexp.prototype.flags": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind": "^1.0.2",
|
||||||
|
"define-properties": "^1.2.0",
|
||||||
|
"set-function-name": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/request": {
|
"node_modules/request": {
|
||||||
"version": "2.88.2",
|
"version": "2.88.2",
|
||||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
|
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
|
||||||
|
@ -7520,6 +7772,20 @@
|
||||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
|
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/set-function-name": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"define-data-property": "^1.0.1",
|
||||||
|
"functions-have-names": "^1.2.3",
|
||||||
|
"has-property-descriptors": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/setprototypeof": {
|
"node_modules/setprototypeof": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
"prettier": "^3.1.0",
|
"prettier": "^3.1.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-quill": "^2.0.0",
|
||||||
"react-router-dom": "^6.20.1",
|
"react-router-dom": "^6.20.1",
|
||||||
"react-toastify": "^9.1.3",
|
"react-toastify": "^9.1.3",
|
||||||
"socket.io-client": "^4.7.2",
|
"socket.io-client": "^4.7.2",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, memo } from "react";
|
||||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||||
import { useTheme } from "@mui/material/styles";
|
import { useTheme } from "@mui/material/styles";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
|
@ -8,6 +8,8 @@ import DialogActions from "@mui/material/DialogActions";
|
||||||
import Dialog from "@mui/material/Dialog";
|
import Dialog from "@mui/material/Dialog";
|
||||||
import Toolbar from "@mui/material/Toolbar";
|
import Toolbar from "@mui/material/Toolbar";
|
||||||
|
|
||||||
|
import TextEditor from "./TextEditor.jsx";
|
||||||
|
|
||||||
const textFileTypes = ["properties", "txt", "yaml", "yml", "json", "env"];
|
const textFileTypes = ["properties", "txt", "yaml", "yml", "json", "env"];
|
||||||
const imageFileTypes = ["png", "jpeg", "jpg"];
|
const imageFileTypes = ["png", "jpeg", "jpg"];
|
||||||
|
|
||||||
|
@ -19,28 +21,40 @@ export function useFilePreview(isOpen = false) {
|
||||||
return [open, dialogToggle];
|
return [open, dialogToggle];
|
||||||
}
|
}
|
||||||
|
|
||||||
function TextPreview(props) {
|
|
||||||
const { fileText } = props;
|
|
||||||
return <div style={{ whiteSpace: "break-spaces" }}>{fileText}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function FilePreview(props) {
|
export default function FilePreview(props) {
|
||||||
const [fileText, setFileText] = useState();
|
const [fileText, setFileText] = useState();
|
||||||
|
const [modifiedText, setModifiedText] = useState();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const fullScreen = useMediaQuery(theme.breakpoints.down("md"));
|
const fullScreen = useMediaQuery(theme.breakpoints.down("md"));
|
||||||
|
|
||||||
const { previewData, open, dialogToggle } = props;
|
const { previewData, open, dialogToggle, server: serverId } = props;
|
||||||
const { fileData, name } = previewData ?? {};
|
const { fileData, name, filePath } = previewData ?? {};
|
||||||
const ext = name ? name.split(".").pop() : null;
|
const ext = name ? name.split(".").pop() : null;
|
||||||
const isTextFile = textFileTypes.includes(ext);
|
const isTextFile = textFileTypes.includes(ext);
|
||||||
|
|
||||||
async function onPreviewChange() {
|
|
||||||
if (isTextFile) setFileText(await fileData.text());
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onPreviewChange();
|
onPreviewChange();
|
||||||
}, [fileData]);
|
}, [fileData]);
|
||||||
|
const editorChange = (v) => setModifiedText(v);
|
||||||
|
|
||||||
|
async function onPreviewChange() {
|
||||||
|
if (!isTextFile) return;
|
||||||
|
const text = await fileData.text();
|
||||||
|
setFileText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSave() {
|
||||||
|
const formData = new FormData();
|
||||||
|
const blob = new Blob([modifiedText], { type: "plain/text" });
|
||||||
|
formData.append("file", blob, name);
|
||||||
|
formData.append("id", serverId);
|
||||||
|
formData.append("path", filePath);
|
||||||
|
await fetch("/api/files/upload", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
dialogToggle();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
|
@ -62,12 +76,15 @@ export default function FilePreview(props) {
|
||||||
<Toolbar sx={{ display: { sm: "none" } }} />
|
<Toolbar sx={{ display: { sm: "none" } }} />
|
||||||
<DialogTitle>{name}</DialogTitle>
|
<DialogTitle>{name}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<TextPreview fileText={fileText} />
|
<TextEditor text={fileText} onChange={editorChange} />
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button autoFocus onClick={dialogToggle}>
|
<Button autoFocus onClick={dialogToggle}>
|
||||||
Close
|
Close
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button variant="contained" autoFocus onClick={onSave}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|
|
@ -105,7 +105,7 @@ export default function MineclusterFiles(props) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append("file", file);
|
||||||
formData.append("id", serverId);
|
formData.append("id", serverId);
|
||||||
formData.append("path", [...dirStack, name].join("/"));
|
formData.append("path", [...dirStack, file.name].join("/"));
|
||||||
await fetch("/api/files/upload", {
|
await fetch("/api/files/upload", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
|
@ -125,7 +125,8 @@ export default function MineclusterFiles(props) {
|
||||||
function previewFile(file) {
|
function previewFile(file) {
|
||||||
const { name } = file;
|
const { name } = file;
|
||||||
previewServerItem(serverId, [...dirStack, name].join("/")).then(
|
previewServerItem(serverId, [...dirStack, name].join("/")).then(
|
||||||
(fileData) => changePreview(name, fileData),
|
(fileData) =>
|
||||||
|
changePreview(name, fileData, [...dirStack, name].join("/")),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
21
src/components/files/TextEditor.jsx
Normal file
21
src/components/files/TextEditor.jsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import ReactQuill from "react-quill";
|
||||||
|
import { useState, useEffect, useMemo, memo } from "react";
|
||||||
|
import "react-quill/dist/quill.snow.css";
|
||||||
|
|
||||||
|
const buildDelta = (t) => {
|
||||||
|
if (!t) return;
|
||||||
|
const ops = t.split("\n").map((l) => ({ insert: `${l}\n` }));
|
||||||
|
return { ops };
|
||||||
|
};
|
||||||
|
|
||||||
|
function TextEditor(props) {
|
||||||
|
const { text, onChange } = props;
|
||||||
|
const [delta, setDelta] = useState();
|
||||||
|
const constructDelta = useMemo(() => buildDelta(text), [text]);
|
||||||
|
useEffect(() => setDelta(constructDelta), [text]);
|
||||||
|
|
||||||
|
const onEditorChange = (c, d, s, editor) => onChange(editor.getText());
|
||||||
|
|
||||||
|
return <ReactQuill theme="snow" value={delta} onChange={onEditorChange} />;
|
||||||
|
}
|
||||||
|
export default memo(TextEditor, (a, b) => a.text === b.text);
|
41
src/components/server-options/ExtraPortsOption.jsx
Normal file
41
src/components/server-options/ExtraPortsOption.jsx
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import Autocomplete from "@mui/material/Autocomplete";
|
||||||
|
import Chip from "@mui/material/Chip";
|
||||||
|
|
||||||
|
const validatePort = (p) => p !== "25565" && p !== "25575" && p.length < 6;
|
||||||
|
|
||||||
|
export default function ExtraPortsOption(props) {
|
||||||
|
const [extraPorts, setExtraPorts] = useState([]);
|
||||||
|
const { onChange } = props;
|
||||||
|
|
||||||
|
function portChange(e, val, optionType, changedValue) {
|
||||||
|
if (optionType === "clear") {
|
||||||
|
setExtraPorts([]);
|
||||||
|
onChange("extraPorts", []);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!validatePort(changedValue.option))
|
||||||
|
return alert("That port cannot be added/removed as an extra port!");
|
||||||
|
setExtraPorts(val);
|
||||||
|
onChange("extraPorts", val);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Autocomplete
|
||||||
|
multiple
|
||||||
|
id="extra-ports-autocomplete"
|
||||||
|
options={[]}
|
||||||
|
value={extraPorts}
|
||||||
|
onChange={portChange}
|
||||||
|
freeSolo
|
||||||
|
renderInput={(p) => <TextField {...p} label="Extra Ports" />}
|
||||||
|
renderTags={(value, getTagProps) =>
|
||||||
|
value.map((option, index) => {
|
||||||
|
const defaultChipProps = getTagProps({ index });
|
||||||
|
return <Chip label={option} {...defaultChipProps} />;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ import CpuOption, {
|
||||||
import MemoryOption, {
|
import MemoryOption, {
|
||||||
memoryOptions,
|
memoryOptions,
|
||||||
} from "@mcl/components/server-options/MemoryOption.jsx";
|
} from "@mcl/components/server-options/MemoryOption.jsx";
|
||||||
|
import ExtraPortsOption from "@mcl/components/server-options/ExtraPortsOption.jsx";
|
||||||
|
|
||||||
import BackupHostOption from "@mcl/components/server-options/BackupHostOption.jsx";
|
import BackupHostOption from "@mcl/components/server-options/BackupHostOption.jsx";
|
||||||
import BackupBucketOption from "@mcl/components/server-options/BackupBucketOption.jsx";
|
import BackupBucketOption from "@mcl/components/server-options/BackupBucketOption.jsx";
|
||||||
|
@ -35,6 +36,7 @@ const defaultServer = {
|
||||||
serverType: serverTypeOptions[0],
|
serverType: serverTypeOptions[0],
|
||||||
cpu: cpuOptions[0],
|
cpu: cpuOptions[0],
|
||||||
memory: memoryOptions[2], // 1.5GB
|
memory: memoryOptions[2], // 1.5GB
|
||||||
|
extraPorts: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function CreateCoreOptions() {
|
export default function CreateCoreOptions() {
|
||||||
|
@ -79,7 +81,6 @@ export default function CreateCoreOptions() {
|
||||||
).toLowerCase()}`);
|
).toLowerCase()}`);
|
||||||
} else for (var k in s) if (k.startsWith("backup")) delete s[k];
|
} else for (var k in s) if (k.startsWith("backup")) delete s[k];
|
||||||
setSpec(s);
|
setSpec(s);
|
||||||
console.log(s);
|
|
||||||
setBackupEnabled(!backupEnabled);
|
setBackupEnabled(!backupEnabled);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -98,6 +99,7 @@ export default function CreateCoreOptions() {
|
||||||
/>
|
/>
|
||||||
<CpuOption value={spec.cpu} onChange={coreUpdate("cpu")} />
|
<CpuOption value={spec.cpu} onChange={coreUpdate("cpu")} />
|
||||||
<MemoryOption value={spec.memory} onChange={coreUpdate("memory")} />
|
<MemoryOption value={spec.memory} onChange={coreUpdate("memory")} />
|
||||||
|
<ExtraPortsOption onChange={updateSpec} />
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Switch
|
<Switch
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useSearchParams, useNavigate } from "react-router-dom";
|
import { useSearchParams, useNavigate } from "react-router-dom";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import Toolbar from "@mui/material/Toolbar";
|
|
||||||
import FilePreview, {
|
import FilePreview, {
|
||||||
useFilePreview,
|
useFilePreview,
|
||||||
} from "@mcl/components/files/FilePreview.jsx";
|
} from "@mcl/components/files/FilePreview.jsx";
|
||||||
|
@ -18,14 +16,15 @@ export default function Files() {
|
||||||
if (!currentServer) nav("/");
|
if (!currentServer) nav("/");
|
||||||
}, [currentServer]);
|
}, [currentServer]);
|
||||||
|
|
||||||
function changePreview(name, fileData) {
|
function changePreview(name, fileData, filePath) {
|
||||||
setPreviewData({ name, fileData });
|
setPreviewData({ name, fileData, filePath });
|
||||||
dialogToggle();
|
dialogToggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="edit" sx={{ height: "100%" }}>
|
<Box className="edit" sx={{ height: "100%" }}>
|
||||||
<FilePreview
|
<FilePreview
|
||||||
|
server={currentServer}
|
||||||
open={open}
|
open={open}
|
||||||
dialogToggle={dialogToggle}
|
dialogToggle={dialogToggle}
|
||||||
previewData={previewData}
|
previewData={previewData}
|
||||||
|
|
|
@ -52,7 +52,7 @@ export const deleteServerItem = async (serverId, path, isDir) =>
|
||||||
|
|
||||||
export async function previewServerItem(serverId, path) {
|
export async function previewServerItem(serverId, path) {
|
||||||
const resp = await fetchApiCore("/files/item", { id: serverId, path });
|
const resp = await fetchApiCore("/files/item", { id: serverId, path });
|
||||||
if (!resp.status === 200) return console.log("AHHHH");
|
if (resp.status !== 200) return console.log("AHHHH");
|
||||||
const blob = await resp.blob();
|
const blob = await resp.blob();
|
||||||
return blob;
|
return blob;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue