init
This commit is contained in:
commit
82ab96e352
13 changed files with 435 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules/
|
||||||
|
build/
|
1
.npmrc
Normal file
1
.npmrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package-lock=false
|
13
config.example.json
Normal file
13
config.example.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"DisallowedFilenameChars": ["\"","<",">","|","\u0000","\u0001","\u0002","\u0003","\u0004","\u0005","\u0006","\u0007","\b","\t","\n","\u000b","\f","\r","\u000e","\u000f","\u0010","\u0011","\u0012","\u0013","\u0014","\u0015","\u0016","\u0017","\u0018","\u0019","\u001a","\u001b","\u001c","\u001d","\u001e","\u001f",":","*","?","\\","/"],
|
||||||
|
"ListenPort": 8369,
|
||||||
|
"MaxFileSize": 104857600,
|
||||||
|
"BlockedMD5": [],
|
||||||
|
"RateLimit": 10,
|
||||||
|
"VMs": [
|
||||||
|
{
|
||||||
|
"ID": "vm1",
|
||||||
|
"SocketPath": "/tmp/vm1-uploads.sock"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
13
config.json
Normal file
13
config.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"DisallowedFilenameChars": ["\"","<",">","|","\u0000","\u0001","\u0002","\u0003","\u0004","\u0005","\u0006","\u0007","\b","\t","\n","\u000b","\f","\r","\u000e","\u000f","\u0010","\u0011","\u0012","\u0013","\u0014","\u0015","\u0016","\u0017","\u0018","\u0019","\u001a","\u001b","\u001c","\u001d","\u001e","\u001f",":","*","?","\\","/"],
|
||||||
|
"ListenPort": 8369,
|
||||||
|
"MaxFileSize": 104857600,
|
||||||
|
"BlockedMD5": ["038e69dec7760dbf8d52f0f2b0677280"],
|
||||||
|
"RateLimit": 10,
|
||||||
|
"VMs": [
|
||||||
|
{
|
||||||
|
"ID": "forkievm1",
|
||||||
|
"SocketPath": "/tmp/vm1-uploads.sock"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
24
package.json
Normal file
24
package.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "collabvm-agent-server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A server to upload files to a VM",
|
||||||
|
"main": "build/index.js",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
|
"author": "Elijah R",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/msgpack-lite": "^0.1.11",
|
||||||
|
"@types/node": "^20.10.3",
|
||||||
|
"typescript": "^5.3.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/md5": "^2.3.5",
|
||||||
|
"async-mutex": "^0.4.0",
|
||||||
|
"fastify": "^4.24.3",
|
||||||
|
"md5": "^2.3.0",
|
||||||
|
"msgpack-lite": "^0.1.26"
|
||||||
|
}
|
||||||
|
}
|
11
src/IConfig.ts
Normal file
11
src/IConfig.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
export default interface IConfig {
|
||||||
|
DisallowedFilenameChars : string[];
|
||||||
|
ListenPort : number;
|
||||||
|
MaxFileSize : number;
|
||||||
|
BlockedMD5: string[];
|
||||||
|
RateLimit : number;
|
||||||
|
VMs : {
|
||||||
|
ID : string;
|
||||||
|
SocketPath : string;
|
||||||
|
}[]
|
||||||
|
}
|
9
src/Protocol.ts
Normal file
9
src/Protocol.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
export interface ProtocolMessage {
|
||||||
|
Operation : ProtocolOperation;
|
||||||
|
Filename? : string;
|
||||||
|
FileData? : Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ProtocolOperation {
|
||||||
|
UploadFile = 0,
|
||||||
|
}
|
30
src/Ratelimit.ts
Normal file
30
src/Ratelimit.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
export default class RateLimit {
|
||||||
|
private cooldown : number;
|
||||||
|
private ready : boolean;
|
||||||
|
private interval? : NodeJS.Timeout;
|
||||||
|
private timeRemaining : number;
|
||||||
|
constructor(cooldown : number) {
|
||||||
|
this.cooldown = cooldown;
|
||||||
|
this.ready = true;
|
||||||
|
this.timeRemaining = 0;
|
||||||
|
}
|
||||||
|
Limit() : boolean {
|
||||||
|
if (this.ready) {
|
||||||
|
this.ready = false;
|
||||||
|
this.timeRemaining = this.cooldown;
|
||||||
|
this.interval = setInterval(() => {
|
||||||
|
this.timeRemaining--;
|
||||||
|
if (this.timeRemaining <= 0) {
|
||||||
|
this.ready = true;
|
||||||
|
clearInterval(this.interval!);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GetRemaining() : number {
|
||||||
|
return this.timeRemaining;
|
||||||
|
}
|
||||||
|
}
|
63
src/VM.ts
Normal file
63
src/VM.ts
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import { Socket } from "net";
|
||||||
|
import * as protocol from './Protocol.js'
|
||||||
|
import * as msgpack from 'msgpack-lite';
|
||||||
|
import {Mutex} from 'async-mutex';
|
||||||
|
import log from './log.js';
|
||||||
|
|
||||||
|
export default class VM {
|
||||||
|
#socketpath : string;
|
||||||
|
#socket : Socket;
|
||||||
|
#writeLock : Mutex = new Mutex();
|
||||||
|
connected : boolean = false;
|
||||||
|
constructor(socketpath : string) {
|
||||||
|
this.#socketpath = socketpath;
|
||||||
|
this.#socket = new Socket();
|
||||||
|
this.#socket.connect(socketpath);
|
||||||
|
this.#socket.on('connect', () => {
|
||||||
|
this.connected = true;
|
||||||
|
log("INFO", `Connected to VM at ${socketpath}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
UploadFile(filename : string, data : Buffer) : Promise<void> {
|
||||||
|
return new Promise(async (res, rej) => {
|
||||||
|
const msg : protocol.ProtocolMessage = {
|
||||||
|
Operation: protocol.ProtocolOperation.UploadFile,
|
||||||
|
Filename: filename,
|
||||||
|
FileData: data
|
||||||
|
};
|
||||||
|
var payload = msgpack.encode(msg);
|
||||||
|
var header = Buffer.alloc(4);
|
||||||
|
header.writeUInt32LE(payload.length);
|
||||||
|
// Make sure we dont write two messages at the same time
|
||||||
|
var release = await this.#writeLock.acquire();
|
||||||
|
await this.#writeData(header);
|
||||||
|
// shit gets fucked if i dont do this
|
||||||
|
await sleep(100);
|
||||||
|
await this.#writeData(payload);
|
||||||
|
// All done
|
||||||
|
release();
|
||||||
|
res();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#writeData(data : Buffer) : Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.#socket.write(data, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms : number) : Promise<void> {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
res();
|
||||||
|
}, ms);
|
||||||
|
})
|
||||||
|
}
|
98
src/index.ts
Normal file
98
src/index.ts
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
import Fastify, {errorCodes} from 'fastify';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import IConfig from './IConfig.js';
|
||||||
|
import VM from './VM.js';
|
||||||
|
import md5 from 'md5';
|
||||||
|
import log from './log.js';
|
||||||
|
import RateLimit from './Ratelimit.js';
|
||||||
|
|
||||||
|
log("INFO", "CollabVM Agent Server Starting up...");
|
||||||
|
// Load the config file
|
||||||
|
var config : IConfig;
|
||||||
|
try {
|
||||||
|
var configraw = readFileSync("./config.json");
|
||||||
|
config = JSON.parse(configraw.toString());
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to load config file: " + (e as Error).message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var VMs = new Map<string, VM>();
|
||||||
|
config.VMs.forEach((v) => {
|
||||||
|
VMs.set(v.ID, new VM(v.SocketPath));
|
||||||
|
});
|
||||||
|
|
||||||
|
var IPs = new Map<string, RateLimit>();
|
||||||
|
|
||||||
|
const app = Fastify({
|
||||||
|
trustProxy: true,
|
||||||
|
bodyLimit: config.MaxFileSize,
|
||||||
|
});
|
||||||
|
app.setErrorHandler((err, req, res) => {
|
||||||
|
if (err.code === "FST_ERR_CTP_BODY_TOO_LARGE") {
|
||||||
|
res.status(413);
|
||||||
|
res.header("Access-Control-Allow-Origin", "*");
|
||||||
|
res.send({ success: false, result: "File too large" });
|
||||||
|
} else {
|
||||||
|
res.send(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
app.addContentTypeParser("application/octet-stream", {parseAs: "buffer"}, (req, body, done) => {
|
||||||
|
done(null, body);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.options("/:vm/*", async (req, res) => {
|
||||||
|
res.header("Access-Control-Allow-Origin", "*");
|
||||||
|
res.header("Access-Control-Allow-Methods", "PUT, OPTIONS");
|
||||||
|
res.header("Access-Control-Allow-Headers", "Content-Type");
|
||||||
|
res.header("Allow", "PUT, OPTIONS");
|
||||||
|
res.status(204);
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
|
app.put("/:vm/:filename", async (req, res) => {
|
||||||
|
res.header("Content-Type", "application/json");
|
||||||
|
res.header("Access-Control-Allow-Origin", "*")
|
||||||
|
const { vm, filename }: {vm : string, filename : string} = (req.params as any);
|
||||||
|
log("INFO", `${vm}: ${req.ip} is uploading "${filename}"`);
|
||||||
|
if (req.headers['content-type'] !== "application/octet-stream") {
|
||||||
|
res.status(400);
|
||||||
|
return { success: false, result: "Invalid content-type" };
|
||||||
|
}
|
||||||
|
if (parseInt(req.headers["content-length"] as string) > config.MaxFileSize) {
|
||||||
|
res.status(400);
|
||||||
|
return { success: false, result: "File too large" };
|
||||||
|
}
|
||||||
|
if (!VMs.has(vm)) {
|
||||||
|
res.status(400);
|
||||||
|
return { success: false, result: "Invalid VM" };
|
||||||
|
}
|
||||||
|
if (config.DisallowedFilenameChars.some((c) => filename.includes(c))) {
|
||||||
|
res.status(400);
|
||||||
|
return { success: false, result: "Filename contains disallowed characters" };
|
||||||
|
}
|
||||||
|
if (IPs.has(req.ip)) {
|
||||||
|
if (!IPs.get(req.ip)!.Limit()) {
|
||||||
|
res.status(429);
|
||||||
|
return { success: false, result: `Please wait ${IPs.get(req.ip)!.GetRemaining()} seconds before uploading another file` }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
IPs.set(req.ip, new RateLimit(config.RateLimit));
|
||||||
|
}
|
||||||
|
var filedata = req.body as Buffer;
|
||||||
|
var hash = md5(filedata);
|
||||||
|
if (config.BlockedMD5.indexOf(hash) !== -1) {
|
||||||
|
res.status(400);
|
||||||
|
log("INFO", `${vm}: ${req.ip} tried to upload "${filename}" with blocked MD5 ${hash}`);
|
||||||
|
return { success: false, result: "That file is not allowed" };
|
||||||
|
}
|
||||||
|
await VMs.get(vm)!.UploadFile(filename, filedata);
|
||||||
|
log("INFO", `${vm}: ${req.ip} uploaded "${filename}" with MD5 ${hash}`);
|
||||||
|
return { success: true, result: "File uploaded" };
|
||||||
|
});
|
||||||
|
|
||||||
|
log("INFO", "Starting HTTP server on port " + config.ListenPort);
|
||||||
|
app.listen({
|
||||||
|
port: config.ListenPort,
|
||||||
|
host: "127.0.0.1"
|
||||||
|
});
|
7
src/log.ts
Normal file
7
src/log.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export default function log(loglevel : string, ...message : string[]) {
|
||||||
|
console[
|
||||||
|
(loglevel === "ERROR" || loglevel === "FATAL") ? "error" :
|
||||||
|
(loglevel === "WARN") ? "warn" :
|
||||||
|
"log"
|
||||||
|
](`[${new Date().toLocaleString()}] [${loglevel}]`, ...message);
|
||||||
|
}
|
109
tsconfig.json
Normal file
109
tsconfig.json
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
|
|
||||||
|
/* Projects */
|
||||||
|
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||||
|
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||||
|
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||||
|
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||||
|
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||||
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
|
/* Language and Environment */
|
||||||
|
"target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||||
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
|
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||||
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
|
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||||
|
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||||
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
|
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||||
|
|
||||||
|
/* Modules */
|
||||||
|
"module": "es2022", /* Specify what module code is generated. */
|
||||||
|
"rootDir": "./src", /* Specify the root folder within your source files. */
|
||||||
|
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||||
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
|
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||||
|
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||||
|
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||||
|
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||||
|
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||||
|
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||||
|
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||||
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
|
/* JavaScript Support */
|
||||||
|
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||||
|
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||||
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||||
|
|
||||||
|
/* Emit */
|
||||||
|
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||||
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||||
|
"outDir": "./build", /* Specify an output folder for all emitted files. */
|
||||||
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
|
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||||
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
|
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
|
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||||
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||||
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
|
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||||
|
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||||
|
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||||
|
|
||||||
|
/* Interop Constraints */
|
||||||
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
|
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
|
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||||
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
|
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||||
|
|
||||||
|
/* Type Checking */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||||
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
|
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||||
|
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
|
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||||
|
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||||
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
|
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||||
|
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||||
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||||
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
|
||||||
|
/* Completeness */
|
||||||
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
|
}
|
||||||
|
}
|
55
web.js
Normal file
55
web.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
var modalel = document.createElement('div');
|
||||||
|
modalel.innerHTML = `
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content bg-dark text-light">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Upload File</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="alert alert-success" role="alert" style="display:none;" id="agentSuccessAlert"></div>
|
||||||
|
<div class="alert alert-danger" role="alert" style="display:none;" id="agentErrorAlert"></div>
|
||||||
|
<input type="file" class="form-control" id="agentfile"/>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-primary" id="agentUploadBtn">Upload</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
modalel.classList.add('modal');
|
||||||
|
modalel.tabIndex = -1;
|
||||||
|
document.body.appendChild(modalel);
|
||||||
|
var fileinput = modalel.querySelector('#agentfile');
|
||||||
|
var uploadbtn = modalel.querySelector('#agentUploadBtn');
|
||||||
|
var successalert = modalel.querySelector('#agentSuccessAlert');
|
||||||
|
var erroralert = modalel.querySelector('#agentErrorAlert');
|
||||||
|
uploadbtn.addEventListener('click', async () => {
|
||||||
|
if (fileinput.files.length == 0) return;
|
||||||
|
successalert.style.display = 'none';
|
||||||
|
erroralert.style.display = 'none';
|
||||||
|
var file = fileinput.files[0];
|
||||||
|
var result = await fetch(`https://vmup.elijahr.dev/${window.VMName}/${file.name}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: file,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/octet-stream'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var json = await result.json();
|
||||||
|
if (json.success) {
|
||||||
|
successalert.style.display = 'block';
|
||||||
|
successalert.innerText = json.result;
|
||||||
|
} else {
|
||||||
|
erroralert.style.display = 'block';
|
||||||
|
erroralert.innerText = json.result;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var modal = new bootstrap.Modal(modalel);
|
||||||
|
var btn = document.createElement('button');
|
||||||
|
btn.innerHTML = '<i class="fa-solid fa-upload"></i> Upload File';
|
||||||
|
btn.classList.add('btn', 'btn-secondary');
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
modal.show();
|
||||||
|
});
|
||||||
|
document.getElementById('btns').appendChild(btn);
|
Loading…
Reference in a new issue