You've already forked AstralRinth
forked from didirus/AstralRinth
* fix Signed-off-by: Evan Song <theevansong@gmail.com> * fix Signed-off-by: Evan Song <theevansong@gmail.com> * refactor(fileitem): optimize Signed-off-by: Evan Song <theevansong@gmail.com> * chore(fileitem): fixed width timestamp Signed-off-by: Evan Song <theevansong@gmail.com> * fix(fileitem): allow editing json5/jsonc Signed-off-by: Evan Song <theevansong@gmail.com> * feat: motd pt 1, auto backups scaffolding, editing navbar changes * feat: fancy sidebar animations * fix: files * fix: files pt2 * fix: faulty name validation disallowing spaces in file names Signed-off-by: Evan Song <theevansong@gmail.com> * refactor: fileitem props Signed-off-by: Evan Song <theevansong@gmail.com> * fix: upload files not refreshing files list Signed-off-by: Evan Song <theevansong@gmail.com> * fix(imgviewer): handle invalid/empty images Signed-off-by: Evan Song <theevansong@gmail.com> * fix: return of the sticky files header Signed-off-by: Evan Song <theevansong@gmail.com> * chore: prevent servericon from shrinking Signed-off-by: Evan Song <theevansong@gmail.com> * fix: wtf were we thinking with this anyway Signed-off-by: Evan Song <theevansong@gmail.com> * fix: further mobile optimization Signed-off-by: Evan Song <theevansong@gmail.com> * chore: propagate margin Signed-off-by: Evan Song <theevansong@gmail.com> * chore: truncation fixes Signed-off-by: Evan Song <theevansong@gmail.com> * fix: track navbar with sentinel Signed-off-by: Evan Song <theevansong@gmail.com> * chore: clean Signed-off-by: Evan Song <theevansong@gmail.com> * fix(files): a11y Signed-off-by: Evan Song <theevansong@gmail.com> * chore: improve inspector styles Signed-off-by: Evan Song <theevansong@gmail.com> * chore: clean Signed-off-by: Evan Song <theevansong@gmail.com> * feat: console preformance improvements, decrease blur * feat(mobile): new server header * fix: linting * fix: useless z indeces Signed-off-by: Evan Song <theevansong@gmail.com> * chore: adjust file filter names Signed-off-by: Evan Song <theevansong@gmail.com> * feat(files): true breadcrumbs Signed-off-by: Evan Song <theevansong@gmail.com> * fix(marketing): make custom responsive * fix(marketing): mobile file manager card * feat: trackable navtabs Signed-off-by: Evan Song <theevansong@gmail.com> * fix: oh no Signed-off-by: Evan Song <theevansong@gmail.com> * fix: smartly truncate Signed-off-by: Evan Song <theevansong@gmail.com> * fix(terminal): z-indexes * fix: autofocus more inputs Signed-off-by: Evan Song <theevansong@gmail.com> * fix: color Signed-off-by: Evan Song <theevansong@gmail.com> * chore: adjust copy Signed-off-by: Evan Song <theevansong@gmail.com> * chore: backup modal usability improvements Signed-off-by: Evan Song <theevansong@gmail.com> * fix: padding Signed-off-by: Evan Song <theevansong@gmail.com> * chore: title Signed-off-by: Evan Song <theevansong@gmail.com> * fix(content): update banner mobile support * fix: server listing icons Signed-off-by: Evan Song <theevansong@gmail.com> * fix: ignore clicks in server listing for labels Signed-off-by: Evan Song <theevansong@gmail.com> * feat(mobile): backup card * fix(backups): make plural conditional * fix: debounce file item selectitem Signed-off-by: Evan Song <theevansong@gmail.com> * fix: lint Signed-off-by: Evan Song <theevansong@gmail.com> * stuff Signed-off-by: Evan Song <theevansong@gmail.com> * fix: temp sidebar fix until i can be smart * chore: clean Signed-off-by: Evan Song <theevansong@gmail.com> * chore: explictly set button type in file modals Signed-off-by: Evan Song <theevansong@gmail.com> * fix: properly sort backups Signed-off-by: Evan Song <theevansong@gmail.com> * feat: add getautobackup method to pyroservers Signed-off-by: Evan Song <theevansong@gmail.com> * choer: update autobackup params Signed-off-by: Evan Song <theevansong@gmail.com> * chore: update autobackup methods (REALLY GUYS) Signed-off-by: Evan Song <theevansong@gmail.com> * feat: implement autobackups Signed-off-by: Evan Song <theevansong@gmail.com> * feat: implement backup-while-running preference Signed-off-by: Evan Song <theevansong@gmail.com> * feat: make server labels a component * feat: implement 'All details' modal * fix(mobile): server manage page * feat(files): mobile compatible * fix(info labels): wrap * chore(inspector): clean Signed-off-by: Evan Song <theevansong@gmail.com> * fix(backup settings): swap + and - * fix(manage): new -> plans instead of modal * feat: more small mobile fixes * fix(auto backup modal): manual input validation * fix(file browse navbar): home margin * feat(purchase modal): mobile support * fix(marketing): faded line alignments * feat: add servers to mobile nav * feat(network): dns record fixes * feat: make all settings work on mobile * fix(loader settings): modpack mobile * chore: clean Signed-off-by: Evan Song <theevansong@gmail.com> * feat(marketing): add 'Manage your servers' button * fix(marketing): only check servers if logged in * fix(network): allocation edit & delete button * fix(backups): use UiServersTeleportOverflowMenu * chore: linting * chore: but here comes the sentence case Signed-off-by: Evan Song <theevansong@gmail.com> * feat(marketing): make buttons consistent * lint Signed-off-by: Evan Song <theevansong@gmail.com> * fix(loader): prevent multiline version names in dropdown Signed-off-by: Evan Song <theevansong@gmail.com> * lint Signed-off-by: Evan Song <theevansong@gmail.com> * fix: copy Signed-off-by: Evan Song <theevansong@gmail.com> * fix: sentence case Signed-off-by: Evan Song <theevansong@gmail.com> * fix: linting * chore: rename dumbass preference key Signed-off-by: Evan Song <theevansong@gmail.com> * refactor: rewrite power action buttons Signed-off-by: Evan Song <theevansong@gmail.com> * fix: robust download logic Signed-off-by: Evan Song <theevansong@gmail.com> * fix(loader mobile): modpack dropdown width * fix: sentence case * fix(save & 'working on it'): look good on mobile * fix(TeleportDropdown): width * fix(inspecting error): mobile * fix: show action button dropdown when installing * fix(navtabs): temp fix for mobile scrolling issue * fix(install error): mobile compatible * chore: just remove tracking Signed-off-by: Evan Song <theevansong@gmail.com> * chore: clean Signed-off-by: Evan Song <theevansong@gmail.com> * chore: clean Signed-off-by: Evan Song <theevansong@gmail.com> * fix: cleanup * fix: broken svg clr in checkbox when using experimental styles Signed-off-by: Evan Song <theevansong@gmail.com> * chore: adjust vanilla icon Signed-off-by: Evan Song <theevansong@gmail.com> * chore: adjust loader props Signed-off-by: Evan Song <theevansong@gmail.com> * revert changes to serversidebar Signed-off-by: Evan Song <theevansong@gmail.com> * fix: server properties flicker Signed-off-by: Evan Song <theevansong@gmail.com> * fix(backups): plural * fix: cases where the telepoverflow would clash with viewport edge Signed-off-by: Evan Song <theevansong@gmail.com> * feat(backups): auto-backups label * fix(network): titlecase * feat(fileitem): new rename icon * fix(properties): wiki proper noun * fix: disable motd for the time being * chore: adjust wording for power conifmration Signed-off-by: Evan Song <theevansong@gmail.com> * feat: "external" to billing Signed-off-by: Evan Song <theevansong@gmail.com> * fix: icon Signed-off-by: Evan Song <theevansong@gmail.com> * fix: add EULA checkbox * chore: clean Signed-off-by: Evan Song <theevansong@gmail.com> * me and bro deciding which case rules to enforce Signed-off-by: Evan Song <theevansong@gmail.com> * feat(sftp): copy address & username, launch tooltip * feat(files): better move * chore: attempt to mitigate excessive stack depth type Signed-off-by: Evan Song <theevansong@gmail.com> * fix(loader): prevent versions 1.2.4 and below * feat(dns table): placeholder improvements * feat(pyroServer): error handling * fix: intrinsic size on loader icon Signed-off-by: Evan Song <theevansong@gmail.com> * chore: adjust wording Signed-off-by: Evan Song <theevansong@gmail.com> * fix: sentence case Signed-off-by: Evan Song <theevansong@gmail.com> * chore: adjust wording Signed-off-by: Evan Song <theevansong@gmail.com> * fix: types Signed-off-by: Evan Song <theevansong@gmail.com> * fix: "implemented" key in preference Signed-off-by: Evan Song <theevansong@gmail.com> * feat(connection lost): redesign * feat(connection error): make icon orange * fix: cleanup * chore(connection lost): redesign pt 2 Signed-off-by: Evan Song <theevansong@gmail.com> * fix: OOOOHHH MY GOD Signed-off-by: Evan Song <theevansong@gmail.com> * feat: implement capacity api on marketing Signed-off-by: Evan Song <theevansong@gmail.com> * chore: update createdat backup type Signed-off-by: Evan Song <theevansong@gmail.com> * refactor: all of backups Signed-off-by: Evan Song <theevansong@gmail.com> * chore: update backup types Signed-off-by: Evan Song <theevansong@gmail.com> * refactor: backups pt 2 Signed-off-by: Evan Song <theevansong@gmail.com> * fix: comically small icons Signed-off-by: Evan Song <theevansong@gmail.com> * chore: align designs Signed-off-by: Evan Song <theevansong@gmail.com> * chore: hide ram graph if ram as bytes enabled Signed-off-by: Evan Song <theevansong@gmail.com> * base add content page * Fix conflict * feat(content): mobile-compatible header, sticky * fix(marketing): md instead of sm for custom * fix: compiler macro warning Signed-off-by: Evan Song <theevansong@gmail.com> * again Signed-off-by: Evan Song <theevansong@gmail.com> * fix: loader type error Signed-off-by: Evan Song <theevansong@gmail.com> * fix: default uptime seconds prop Signed-off-by: Evan Song <theevansong@gmail.com> * fix: hydration errors on server listing Signed-off-by: Evan Song <theevansong@gmail.com> * feat: move custom URL to general Signed-off-by: Evan Song <theevansong@gmail.com> * feat: indiviudally checkj capacities Signed-off-by: Evan Song <theevansong@gmail.com> * fix: falsey Signed-off-by: Evan Song <theevansong@gmail.com> * fix: missing prop Signed-off-by: Evan Song <theevansong@gmail.com> * chore: Derive On That Thang Signed-off-by: Evan Song <theevansong@gmail.com> * chore: adjust gap Signed-off-by: Evan Song <theevansong@gmail.com> * fix: add default name for backups * fix: the backup number should PROBABLY be computed lol * fix(backups): truncate text, mobile fixes * fix(loader): modpack mobile fix * feat(plans): add vcpus * fix(backup modal): blank by default, maxlength * fix(subdomain): separate length & valid chars * feat: mrpack installs functionality (untested), forbidden handling, backups grammar * feat(content): make responsive on mobile * fix: disable plan buttons separately * fix(backup modal): update name max length * fix(purchase): wrapping on eula, eula link * fix: move skeleton * fix(server mobile header): truncation * fix(server header): proper alignment * Finish content page fixes * fix: who up rinthing Signed-off-by: Evan Song <theevansong@gmail.com> * wip Signed-off-by: Evan Song <theevansong@gmail.com> * fix(staging & email banner): z-index * feat: make eula tickbox more visible * fix: move "powered by pyro" below buttons on hero * fix: oops sorry ellie, also updated the main screenshot * feat: update content screenshot * fix: content page card should hide image on lg * feat: hide total storage for now * fix: terminal card now uses terminal icon * fix(marketing): make medium plan card border solid * feat: modloader card, move pyro BACK below buttons, beta release pill * fix: spinning logo should be behind hero * feat: surgically remove the hero's massive forehead * feat(marketing): mobile UI screenshot * fix(hero): z-index goes over mobile nav * fix: consistent borders, files breakpoints * chore: update turbo * chore: adjust hero sizing Signed-off-by: Evan Song <theevansong@gmail.com> * chore: mention region restrictions * chore: double check if we are at capcity Signed-off-by: Evan Song <theevansong@gmail.com> * fix: measure twice cut once Signed-off-by: Evan Song <theevansong@gmail.com> * chore: bro cut twice and measured once 💀 Signed-off-by: Evan Song <theevansong@gmail.com> * fix(marketing): login first * fix: out of capacity text when logged out * fix(slider): reset some values for frontend * feat: wip hero section Signed-off-by: Evan Song <theevansong@gmail.com> * New navigation to support the new products (#2879) * Nav * oops extra file * feat: mrpack uploading with existing modpack, fix: choose modpack duplicate * chore: clean Signed-off-by: Evan Song <theevansong@gmail.com> * feat: update features section Signed-off-by: Evan Song <theevansong@gmail.com> * Nav adjustments * fix: server manager empty state clashing with loading state Signed-off-by: Evan Song <theevansong@gmail.com> * chore: query param hard Signed-off-by: Evan Song <theevansong@gmail.com> * fix: do not count uptime if crashed Signed-off-by: Evan Song <theevansong@gmail.com> * fix: grammar Signed-off-by: Evan Song <theevansong@gmail.com> * hide hero img on lg breakpoints * Make plugins a plug * chore: prep for buffered text selection terminal Signed-off-by: Evan Song <theevansong@gmail.com> * fix: marketing responsive stuff, n fixes * fix hoverable prop * fix: edit mod spacing * fix: type error for display name in dropdown Signed-off-by: Evan Song <theevansong@gmail.com> * feat: custom plans * fix: no more console.log * fix: properly linked prop label Signed-off-by: Evan Song <theevansong@gmail.com> * fix(install hero mobile): padding * fix: prevent x overflow on servers page Signed-off-by: Evan Song <theevansong@gmail.com> * fix lint oh ym fucking god yal Signed-off-by: Evan Song <theevansong@gmail.com> * Migrate modpack install to search * fix(custom plan): warning icon variable * fix: loading probally and modal loader things * fix(marketing): login icon colours * fix(marketing): responsiveness * fix(marketing): responsiveness v2 * fix: sync button for icon tm * fix(marketing): responsiveness v3 * fix: hero image Signed-off-by: Evan Song <theevansong@gmail.com> * chore: clean Signed-off-by: Evan Song <theevansong@gmail.com> * chore: switch to cdn links Signed-off-by: Evan Song <theevansong@gmail.com> * chore: switch to cdn links Signed-off-by: Evan Song <theevansong@gmail.com> * chore: switch to cdn links Signed-off-by: Evan Song <theevansong@gmail.com> * chore: switch to cdn links Signed-off-by: Evan Song <theevansong@gmail.com> * Remove prod override --------- Signed-off-by: Evan Song <theevansong@gmail.com> Co-authored-by: Evan Song <theevansong@gmail.com> Co-authored-by: TheWander02 <48934424+thewander02@users.noreply.github.com> Co-authored-by: he3als <65787561+he3als@users.noreply.github.com> Co-authored-by: Evan Song <52982404+ferothefox@users.noreply.github.com> Co-authored-by: Lio <git@lio.cat> Co-authored-by: Jai A <jaiagr+gpg@pm.me> Co-authored-by: not-nullptr <needhelpwithrift@gmail.com> Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com> Co-authored-by: Prospector <prospectordev@gmail.com> Co-authored-by: sticks <tanner@teamhydra.dev>
1294 lines
34 KiB
TypeScript
1294 lines
34 KiB
TypeScript
// usePyroServer is a composable that interfaces with the REDACTED API to get data and control the users server
|
|
import { $fetch, FetchError } from "ofetch";
|
|
|
|
interface PyroFetchOptions {
|
|
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
contentType?: string;
|
|
body?: Record<string, any>;
|
|
version?: number;
|
|
override?: {
|
|
url?: string;
|
|
token?: string;
|
|
};
|
|
retry?: boolean;
|
|
}
|
|
|
|
async function PyroFetch<T>(path: string, options: PyroFetchOptions = {}): Promise<T> {
|
|
const config = useRuntimeConfig();
|
|
const auth = await useAuth();
|
|
const authToken = auth.value?.token;
|
|
|
|
if (!authToken) {
|
|
throw new PyroFetchError("Cannot pyrofetch without auth", 10000);
|
|
}
|
|
|
|
const { method = "GET", contentType = "application/json", body, version = 0, override } = options;
|
|
|
|
const base = (import.meta.server ? config.pyroBaseUrl : config.public.pyroBaseUrl)?.replace(
|
|
/\/$/,
|
|
"",
|
|
);
|
|
|
|
if (!base) {
|
|
throw new PyroFetchError(
|
|
"Cannot pyrofetch without base url. Make sure to set a PYRO_BASE_URL in environment variables",
|
|
10001,
|
|
);
|
|
}
|
|
|
|
const fullUrl = override?.url
|
|
? `https://${override.url}/${path.replace(/^\//, "")}`
|
|
: `${base}/modrinth/v${version}/${path.replace(/^\//, "")}`;
|
|
|
|
type HeadersRecord = Record<string, string>;
|
|
|
|
const headers: HeadersRecord = {
|
|
Authorization: `Bearer ${override?.token ?? authToken}`,
|
|
"Access-Control-Allow-Headers": "Authorization",
|
|
"User-Agent": "Pyro/1.0 (https://pyro.host)",
|
|
Vary: "Accept, Origin",
|
|
"Content-Type": contentType,
|
|
};
|
|
|
|
if (import.meta.client && typeof window !== "undefined") {
|
|
headers.Origin = window.location.origin;
|
|
}
|
|
|
|
try {
|
|
const response = await $fetch<T>(fullUrl, {
|
|
method,
|
|
headers,
|
|
body: body && contentType === "application/json" ? JSON.stringify(body) : body ?? undefined,
|
|
timeout: 10000,
|
|
retry: options.retry !== false ? (method === "GET" ? 3 : 0) : 0,
|
|
});
|
|
return response;
|
|
} catch (error) {
|
|
console.error("[PYROSERVERS]:", error);
|
|
if (error instanceof FetchError) {
|
|
const statusCode = error.response?.status;
|
|
const statusText = error.response?.statusText || "Unknown error";
|
|
const errorMessages: { [key: number]: string } = {
|
|
400: "Bad Request",
|
|
401: "Unauthorized",
|
|
403: "Forbidden",
|
|
404: "Not Found",
|
|
405: "Method Not Allowed",
|
|
429: "Too Many Requests",
|
|
500: "Internal Server Error",
|
|
502: "Bad Gateway",
|
|
};
|
|
const message =
|
|
statusCode && statusCode in errorMessages
|
|
? errorMessages[statusCode]
|
|
: `HTTP Error: ${statusCode || "unknown"} ${statusText}`;
|
|
throw new PyroFetchError(`[PYROSERVERS][PYRO] ${message}`, statusCode, error);
|
|
}
|
|
throw new PyroFetchError(
|
|
"[PYROSERVERS][PYRO] An unexpected error occurred during the fetch operation.",
|
|
undefined,
|
|
error as Error,
|
|
);
|
|
}
|
|
}
|
|
|
|
const internalServerRefrence = ref<any>(null);
|
|
|
|
interface License {
|
|
id: string;
|
|
name: string;
|
|
url: string;
|
|
}
|
|
|
|
interface DonationUrl {
|
|
id: string;
|
|
platform: string;
|
|
url: string;
|
|
}
|
|
|
|
interface GalleryItem {
|
|
url: string;
|
|
featured: boolean;
|
|
title: string;
|
|
description: string;
|
|
created: string;
|
|
ordering: number;
|
|
}
|
|
|
|
export interface Project {
|
|
slug: string;
|
|
title: string;
|
|
description: string;
|
|
categories: string[];
|
|
client_side: "required" | "optional";
|
|
server_side: "required" | "optional";
|
|
body: string;
|
|
status: "approved" | "pending" | "rejected";
|
|
requested_status: "approved" | "pending" | "rejected";
|
|
additional_categories: string[];
|
|
issues_url: string;
|
|
source_url: string;
|
|
wiki_url: string;
|
|
discord_url: string;
|
|
donation_urls: DonationUrl[];
|
|
project_type: "mod" | "resourcepack" | "map" | "plugin";
|
|
downloads: number;
|
|
icon_url: string;
|
|
color: number;
|
|
thread_id: string;
|
|
monetization_status: "monetized" | "non-monetized";
|
|
id: string;
|
|
team: string;
|
|
body_url: string | null;
|
|
moderator_message: string | null;
|
|
published: string;
|
|
updated: string;
|
|
approved: string;
|
|
queued: string;
|
|
followers: number;
|
|
license: License;
|
|
versions: string[];
|
|
game_versions: string[];
|
|
loaders: string[];
|
|
gallery: GalleryItem[];
|
|
}
|
|
|
|
interface General {
|
|
server_id: string;
|
|
name: string;
|
|
net: {
|
|
ip: string;
|
|
port: number;
|
|
domain: string;
|
|
};
|
|
game: string;
|
|
backup_quota: number;
|
|
used_backup_quota: number;
|
|
status: string;
|
|
suspension_reason: string;
|
|
loader: string;
|
|
loader_version: string;
|
|
mc_version: string;
|
|
upstream: {
|
|
kind: "modpack" | "mod" | "resourcepack";
|
|
version_id: string;
|
|
project_id: string;
|
|
} | null;
|
|
motd?: string;
|
|
image?: string;
|
|
project?: Project;
|
|
sftp_username: string;
|
|
sftp_password: string;
|
|
sftp_host: string;
|
|
datacenter?: string;
|
|
}
|
|
|
|
interface Allocation {
|
|
port: number;
|
|
name: string;
|
|
}
|
|
|
|
interface Startup {
|
|
invocation: string;
|
|
original_invocation: string;
|
|
jdk_version: "lts8" | "lts11" | "lts17" | "lts21";
|
|
jdk_build: "corretto" | "temurin" | "graal";
|
|
}
|
|
|
|
interface Mod {
|
|
filename: string;
|
|
project_id: string;
|
|
version_id: string;
|
|
name: string;
|
|
version_number: string;
|
|
icon_url: string;
|
|
disabled: boolean;
|
|
}
|
|
|
|
interface Backup {
|
|
id: string;
|
|
name: string;
|
|
created_at: string;
|
|
ongoing: boolean;
|
|
}
|
|
|
|
interface AutoBackupSettings {
|
|
enabled: boolean;
|
|
interval: number;
|
|
}
|
|
|
|
interface JWTAuth {
|
|
url: string;
|
|
token: string;
|
|
}
|
|
|
|
const constructServerProperties = (properties: any): string => {
|
|
let fileContent = `#Minecraft server properties\n#${new Date().toUTCString()}\n`;
|
|
|
|
for (const [key, value] of Object.entries(properties)) {
|
|
if (typeof value === "object") {
|
|
fileContent += `${key}=${JSON.stringify(value)}\n`;
|
|
} else if (typeof value === "boolean") {
|
|
fileContent += `${key}=${value ? "true" : "false"}\n`;
|
|
} else {
|
|
fileContent += `${key}=${value}\n`;
|
|
}
|
|
}
|
|
|
|
return fileContent;
|
|
};
|
|
|
|
const processImage = async (iconUrl: string | undefined) => {
|
|
const image = ref<string | null>(null);
|
|
const auth = await PyroFetch<JWTAuth>(`servers/${internalServerRefrence.value.serverId}/fs`);
|
|
try {
|
|
const fileData = await PyroFetch(`/download?path=/server-icon-original.png`, {
|
|
override: auth,
|
|
retry: false,
|
|
});
|
|
|
|
if (fileData instanceof Blob) {
|
|
if (import.meta.client) {
|
|
const canvas = document.createElement("canvas");
|
|
const ctx = canvas.getContext("2d");
|
|
const img = new Image();
|
|
img.src = URL.createObjectURL(fileData);
|
|
await new Promise<void>((resolve) => {
|
|
img.onload = () => {
|
|
canvas.width = 512;
|
|
canvas.height = 512;
|
|
ctx?.drawImage(img, 0, 0, 512, 512);
|
|
const dataURL = canvas.toDataURL("image/png");
|
|
internalServerRefrence.value.general.image = dataURL;
|
|
image.value = dataURL;
|
|
resolve();
|
|
};
|
|
});
|
|
}
|
|
}
|
|
} catch (error) {
|
|
if (error instanceof PyroFetchError && error.statusCode === 404) {
|
|
console.log("[PYROSERVERS] No server icon found");
|
|
} else {
|
|
console.error(error);
|
|
}
|
|
}
|
|
|
|
if (image.value === null && iconUrl) {
|
|
console.log("iconUrl", iconUrl);
|
|
try {
|
|
const response = await fetch(iconUrl);
|
|
const file = await response.blob();
|
|
const originalfile = new File([file], "server-icon-original.png", {
|
|
type: "image/png",
|
|
});
|
|
if (import.meta.client) {
|
|
const scaledFile = await new Promise<File>((resolve, reject) => {
|
|
const canvas = document.createElement("canvas");
|
|
const ctx = canvas.getContext("2d");
|
|
const img = new Image();
|
|
img.src = URL.createObjectURL(file);
|
|
img.onload = () => {
|
|
canvas.width = 64;
|
|
canvas.height = 64;
|
|
ctx?.drawImage(img, 0, 0, 64, 64);
|
|
canvas.toBlob((blob) => {
|
|
if (blob) {
|
|
const data = new File([blob], "server-icon.png", { type: "image/png" });
|
|
resolve(data);
|
|
} else {
|
|
reject(new Error("Canvas toBlob failed"));
|
|
}
|
|
}, "image/png");
|
|
};
|
|
img.onerror = reject;
|
|
});
|
|
if (scaledFile) {
|
|
await PyroFetch(`/create?path=/server-icon.png&type=file`, {
|
|
method: "POST",
|
|
contentType: "application/octet-stream",
|
|
body: scaledFile,
|
|
override: auth,
|
|
});
|
|
|
|
await PyroFetch(`/create?path=/server-icon-original.png&type=file`, {
|
|
method: "POST",
|
|
contentType: "application/octet-stream",
|
|
body: originalfile,
|
|
override: auth,
|
|
});
|
|
}
|
|
}
|
|
} catch (error) {
|
|
if (error instanceof PyroFetchError && error.statusCode === 404) {
|
|
console.log("[PYROSERVERS] No server icon found");
|
|
} else {
|
|
console.error(error);
|
|
}
|
|
}
|
|
}
|
|
return image.value;
|
|
};
|
|
|
|
// ------------------ GENERAL ------------------ //
|
|
|
|
const sendPowerAction = async (action: string) => {
|
|
try {
|
|
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/power`, {
|
|
method: "POST",
|
|
body: { action },
|
|
});
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
await internalServerRefrence.value.refresh();
|
|
} catch (error) {
|
|
console.error("Error changing power state:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const updateName = async (newName: string) => {
|
|
try {
|
|
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/name`, {
|
|
method: "POST",
|
|
body: { name: newName },
|
|
});
|
|
} catch (error) {
|
|
console.error("Error updating server name:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const reinstallServer = async (
|
|
serverId: string,
|
|
loader: boolean,
|
|
projectId: string,
|
|
versionId?: string,
|
|
loaderVersionId?: string,
|
|
hardReset: boolean = false,
|
|
) => {
|
|
try {
|
|
const hardResetParam = hardReset ? "true" : "false";
|
|
if (loader) {
|
|
if (projectId.toLowerCase() === "neoforge") {
|
|
projectId = "NeoForge";
|
|
}
|
|
await PyroFetch(`servers/${serverId}/reinstall?hard=${hardResetParam}`, {
|
|
method: "POST",
|
|
body: { loader: projectId, loader_version: loaderVersionId, game_version: versionId },
|
|
});
|
|
} else {
|
|
await PyroFetch(`servers/${serverId}/reinstall?hard=${hardResetParam}`, {
|
|
method: "POST",
|
|
body: { project_id: projectId, version_id: versionId },
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error("Error reinstalling server:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const reinstallFromMrpack = async (mrpack: File, hardReset: boolean = false) => {
|
|
const hardResetParam = hardReset ? "true" : "false";
|
|
try {
|
|
const auth = await PyroFetch<JWTAuth>(
|
|
`servers/${internalServerRefrence.value.serverId}/reinstallFromMrpack`,
|
|
);
|
|
|
|
return await PyroFetch(`/reinstallMrpack?hard=${hardResetParam}`, {
|
|
method: "POST",
|
|
contentType: "application/octet-stream",
|
|
body: mrpack,
|
|
override: auth,
|
|
});
|
|
} catch (error) {
|
|
console.error("Error reinstalling from mrpack:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const suspendServer = async (status: boolean) => {
|
|
try {
|
|
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/suspend`, {
|
|
method: "POST",
|
|
body: { suspended: status },
|
|
});
|
|
} catch (error) {
|
|
console.error("Error suspending server:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const fetchConfigFile = async (fileName: string) => {
|
|
try {
|
|
return await PyroFetch(`servers/${internalServerRefrence.value.serverId}/config/${fileName}`);
|
|
} catch (error) {
|
|
console.error("Error fetching config file:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const getMotd = async () => {
|
|
try {
|
|
const props = await downloadFile("/server.properties");
|
|
if (props) {
|
|
const lines = props.split("\n");
|
|
for (const line of lines) {
|
|
if (line.startsWith("motd=")) {
|
|
return line.slice(5);
|
|
}
|
|
}
|
|
}
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const setMotd = async (motd: string) => {
|
|
try {
|
|
const props = (await fetchConfigFile("ServerProperties")) as any;
|
|
if (props) {
|
|
props.motd = motd;
|
|
const newProps = constructServerProperties(props);
|
|
const octetStream = new Blob([newProps], { type: "application/octet-stream" });
|
|
const auth = await await PyroFetch<JWTAuth>(
|
|
`servers/${internalServerRefrence.value.serverId}/fs`,
|
|
);
|
|
|
|
return await PyroFetch(`/update?path=/server.properties`, {
|
|
method: "PUT",
|
|
contentType: "application/octet-stream",
|
|
body: octetStream,
|
|
override: auth,
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error("Error setting motd:", error);
|
|
}
|
|
};
|
|
|
|
// ------------------ MODS ------------------ //
|
|
|
|
const installMod = async (projectId: string, versionId: string) => {
|
|
try {
|
|
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/mods`, {
|
|
method: "POST",
|
|
body: { rinth_ids: { project_id: projectId, version_id: versionId } },
|
|
});
|
|
} catch (error) {
|
|
console.error("Error installing mod:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const removeMod = async (modId: string) => {
|
|
try {
|
|
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/deleteMod`, {
|
|
method: "POST",
|
|
body: {
|
|
path: modId,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error("Error removing mod:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const reinstallMod = async (modId: string, versionId: string) => {
|
|
try {
|
|
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/mods/${modId}`, {
|
|
method: "PUT",
|
|
body: { version_id: versionId },
|
|
});
|
|
} catch (error) {
|
|
console.error("Error reinstalling mod:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// ------------------ BACKUPS ------------------ //
|
|
|
|
const createBackup = async (backupName: string) => {
|
|
try {
|
|
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/backups`, {
|
|
method: "POST",
|
|
body: { name: backupName },
|
|
});
|
|
} catch (error) {
|
|
console.error("Error creating backup:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const renameBackup = async (backupId: string, newName: string) => {
|
|
try {
|
|
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/backups/${backupId}/rename`, {
|
|
method: "POST",
|
|
body: { name: newName },
|
|
});
|
|
} catch (error) {
|
|
console.error("Error renaming backup:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const deleteBackup = async (backupId: string) => {
|
|
try {
|
|
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/backups/${backupId}`, {
|
|
method: "DELETE",
|
|
});
|
|
} catch (error) {
|
|
console.error("Error deleting backup:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const restoreBackup = async (backupId: string) => {
|
|
try {
|
|
await PyroFetch(
|
|
`servers/${internalServerRefrence.value.serverId}/backups/${backupId}/restore`,
|
|
{
|
|
method: "POST",
|
|
},
|
|
);
|
|
} catch (error) {
|
|
console.error("Error restoring backup:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const downloadBackup = async (backupId: string) => {
|
|
try {
|
|
return await PyroFetch(`servers/${internalServerRefrence.value.serverId}/backups/${backupId}`);
|
|
} catch (error) {
|
|
console.error("Error downloading backup:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const updateAutoBackup = async (autoBackup: "enable" | "disable", interval: number) => {
|
|
try {
|
|
return await PyroFetch(`servers/${internalServerRefrence.value.serverId}/autobackup`, {
|
|
method: "POST",
|
|
body: { set: autoBackup, interval },
|
|
});
|
|
} catch (error) {
|
|
console.error("Error updating auto backup:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const getAutoBackup = async () => {
|
|
try {
|
|
return await PyroFetch(`servers/${internalServerRefrence.value.serverId}/autobackup`);
|
|
} catch (error) {
|
|
console.error("Error getting auto backup settings:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// ------------------ NETWORK ------------------ //
|
|
|
|
const reserveAllocation = async (name: string): Promise<Allocation> => {
|
|
try {
|
|
return await PyroFetch<Allocation>(
|
|
`servers/${internalServerRefrence.value.serverId}/allocations?name=${name}`,
|
|
{
|
|
method: "POST",
|
|
},
|
|
);
|
|
} catch (error) {
|
|
console.error("Error reserving new allocation:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const updateAllocation = async (port: number, name: string) => {
|
|
try {
|
|
await PyroFetch(
|
|
`servers/${internalServerRefrence.value.serverId}/allocations/${port}?name=${name}`,
|
|
{
|
|
method: "PUT",
|
|
},
|
|
);
|
|
} catch (error) {
|
|
console.error("Error updating allocations:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const deleteAllocation = async (port: number) => {
|
|
try {
|
|
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/allocations/${port}`, {
|
|
method: "DELETE",
|
|
});
|
|
} catch (error) {
|
|
console.error("Error deleting allocation:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const checkSubdomainAvailability = async (subdomain: string): Promise<{ available: boolean }> => {
|
|
try {
|
|
return (await PyroFetch(`subdomains/${subdomain}/isavailable`)) as { available: boolean };
|
|
} catch (error) {
|
|
console.error("Error checking subdomain availability:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const changeSubdomain = async (subdomain: string) => {
|
|
try {
|
|
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/subdomain`, {
|
|
method: "POST",
|
|
body: { subdomain },
|
|
});
|
|
} catch (error) {
|
|
console.error("Error changing subdomain:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// ------------------ STARTUP ------------------ //
|
|
|
|
const updateStartupSettings = async (
|
|
invocation: string,
|
|
jdkVersion: "lts8" | "lts11" | "lts17" | "lts21",
|
|
jdkBuild: "corretto" | "temurin" | "graal",
|
|
) => {
|
|
try {
|
|
await PyroFetch(`servers/${internalServerRefrence.value.serverId}/startup`, {
|
|
method: "POST",
|
|
body: {
|
|
invocation: invocation || null,
|
|
jdk_version: jdkVersion || null,
|
|
jdk_build: jdkBuild || null,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error("Error updating startup settings:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// ------------------ FS ------------------ //
|
|
|
|
const retryWithAuth = async (requestFn: () => Promise<any>) => {
|
|
try {
|
|
return await requestFn();
|
|
} catch (error) {
|
|
if (error instanceof PyroFetchError && error.statusCode === 401) {
|
|
await internalServerRefrence.value.refresh(["fs"]);
|
|
return await requestFn();
|
|
}
|
|
}
|
|
};
|
|
|
|
const listDirContents = (path: string, page: number, pageSize: number) => {
|
|
return retryWithAuth(async () => {
|
|
return await PyroFetch(`/list?path=${path}&page=${page}&page_size=${pageSize}`, {
|
|
override: internalServerRefrence.value.fs.auth,
|
|
retry: false,
|
|
});
|
|
});
|
|
};
|
|
|
|
const createFileOrFolder = (path: string, type: "file" | "directory") => {
|
|
return retryWithAuth(async () => {
|
|
return await PyroFetch(`/create?path=${path}&type=${type}`, {
|
|
method: "POST",
|
|
contentType: "application/octet-stream",
|
|
override: internalServerRefrence.value.fs.auth,
|
|
});
|
|
});
|
|
};
|
|
|
|
const uploadFile = (path: string, file: File) => {
|
|
return retryWithAuth(async () => {
|
|
return await PyroFetch(`/create?path=${path}&type=file`, {
|
|
method: "POST",
|
|
contentType: "application/octet-stream",
|
|
body: file,
|
|
override: internalServerRefrence.value.fs.auth,
|
|
});
|
|
});
|
|
};
|
|
|
|
const renameFileOrFolder = (path: string, name: string) => {
|
|
const pathName = path.split("/").slice(0, -1).join("/") + "/" + name;
|
|
return retryWithAuth(async () => {
|
|
return await PyroFetch(`/move`, {
|
|
method: "POST",
|
|
override: internalServerRefrence.value.fs.auth,
|
|
body: {
|
|
source: path,
|
|
destination: pathName,
|
|
},
|
|
});
|
|
});
|
|
};
|
|
|
|
const updateFile = (path: string, content: string) => {
|
|
const octetStream = new Blob([content], { type: "application/octet-stream" });
|
|
return retryWithAuth(async () => {
|
|
return await PyroFetch(`/update?path=${path}`, {
|
|
method: "PUT",
|
|
contentType: "application/octet-stream",
|
|
body: octetStream,
|
|
override: internalServerRefrence.value.fs.auth,
|
|
});
|
|
});
|
|
};
|
|
|
|
const createMissingFolders = async (path: string) => {
|
|
if (path.startsWith("/")) {
|
|
path = path.substring(1);
|
|
}
|
|
const folders = path.split("/");
|
|
console.log(folders);
|
|
let currentPath = "";
|
|
|
|
for (const folder of folders) {
|
|
currentPath += "/" + folder;
|
|
try {
|
|
await createFileOrFolder(currentPath, "directory");
|
|
} catch {}
|
|
}
|
|
};
|
|
|
|
const moveFileOrFolder = (path: string, newPath: string) => {
|
|
return retryWithAuth(async () => {
|
|
console.log(path);
|
|
console.log(newPath);
|
|
console.log(newPath.substring(0, newPath.lastIndexOf("/")));
|
|
await createMissingFolders(newPath.substring(0, newPath.lastIndexOf("/")));
|
|
|
|
return await PyroFetch(`/move`, {
|
|
method: "POST",
|
|
override: internalServerRefrence.value.fs.auth,
|
|
body: {
|
|
source: path,
|
|
destination: newPath,
|
|
},
|
|
});
|
|
});
|
|
};
|
|
|
|
const deleteFileOrFolder = (path: string, recursive: boolean) => {
|
|
const encodedPath = encodeURIComponent(path);
|
|
return retryWithAuth(async () => {
|
|
return await PyroFetch(`/delete?path=${encodedPath}&recursive=${recursive}`, {
|
|
method: "DELETE",
|
|
override: internalServerRefrence.value.fs.auth,
|
|
});
|
|
});
|
|
};
|
|
|
|
const downloadFile = (path: string, raw?: boolean) => {
|
|
return retryWithAuth(async () => {
|
|
const fileData = await PyroFetch(`/download?path=${path}`, {
|
|
override: internalServerRefrence.value.fs.auth,
|
|
});
|
|
|
|
if (fileData instanceof Blob) {
|
|
if (raw) {
|
|
return fileData;
|
|
} else {
|
|
return await fileData.text();
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
const modules: any = {
|
|
general: {
|
|
get: async (serverId: string) => {
|
|
try {
|
|
const data = await PyroFetch<General>(`servers/${serverId}`);
|
|
// TODO: temp hack to fix hydration error
|
|
if (data.upstream?.project_id) {
|
|
const res = await $fetch(
|
|
`https://api.modrinth.com/v2/project/${data.upstream.project_id}`,
|
|
);
|
|
data.project = res as Project;
|
|
}
|
|
if (import.meta.client) {
|
|
data.image = (await processImage(data.project?.icon_url)) ?? undefined;
|
|
}
|
|
const motd = await getMotd();
|
|
if (motd === "A Minecraft Server") {
|
|
await setMotd(
|
|
`§b${data.project?.title || data.loader + " " + data.mc_version} §f♦ §aModrinth Servers`,
|
|
);
|
|
}
|
|
data.motd = motd;
|
|
return data;
|
|
} catch (error) {
|
|
internalServerRefrence.value.setError(error);
|
|
return undefined;
|
|
}
|
|
},
|
|
updateName,
|
|
power: sendPowerAction,
|
|
reinstall: reinstallServer,
|
|
reinstallFromMrpack,
|
|
suspend: suspendServer,
|
|
getMotd,
|
|
setMotd,
|
|
fetchConfigFile,
|
|
},
|
|
mods: {
|
|
get: async (serverId: string) => {
|
|
try {
|
|
const mods = await PyroFetch<Mod[]>(`servers/${serverId}/mods`);
|
|
return {
|
|
data:
|
|
internalServerRefrence.value.error === undefined
|
|
? mods.sort((a, b) => (a?.name ?? "").localeCompare(b?.name ?? ""))
|
|
: [],
|
|
};
|
|
} catch (error) {
|
|
internalServerRefrence.value.setError(error);
|
|
return undefined;
|
|
}
|
|
},
|
|
install: installMod,
|
|
remove: removeMod,
|
|
reinstall: reinstallMod,
|
|
},
|
|
backups: {
|
|
get: async (serverId: string) => {
|
|
try {
|
|
return { data: await PyroFetch<Backup[]>(`servers/${serverId}/backups`) };
|
|
} catch (error) {
|
|
internalServerRefrence.value.setError(error);
|
|
return undefined;
|
|
}
|
|
},
|
|
create: createBackup,
|
|
rename: renameBackup,
|
|
delete: deleteBackup,
|
|
restore: restoreBackup,
|
|
download: downloadBackup,
|
|
updateAutoBackup,
|
|
getAutoBackup,
|
|
},
|
|
network: {
|
|
get: async (serverId: string) => {
|
|
try {
|
|
return { allocations: await PyroFetch<Allocation[]>(`servers/${serverId}/allocations`) };
|
|
} catch (error) {
|
|
internalServerRefrence.value.setError(error);
|
|
return undefined;
|
|
}
|
|
},
|
|
reserveAllocation,
|
|
updateAllocation,
|
|
deleteAllocation,
|
|
checkSubdomainAvailability,
|
|
changeSubdomain,
|
|
},
|
|
startup: {
|
|
get: async (serverId: string) => {
|
|
try {
|
|
return await PyroFetch<Startup>(`servers/${serverId}/startup`);
|
|
} catch (error) {
|
|
internalServerRefrence.value.setError(error);
|
|
return undefined;
|
|
}
|
|
},
|
|
update: updateStartupSettings,
|
|
},
|
|
ws: {
|
|
get: async (serverId: string) => {
|
|
try {
|
|
return await PyroFetch<JWTAuth>(`servers/${serverId}/ws`);
|
|
} catch (error) {
|
|
internalServerRefrence.value.setError(error);
|
|
return undefined;
|
|
}
|
|
},
|
|
},
|
|
fs: {
|
|
get: async (serverId: string) => {
|
|
try {
|
|
return { auth: await PyroFetch<JWTAuth>(`servers/${serverId}/fs`) };
|
|
} catch (error) {
|
|
internalServerRefrence.value.setError(error);
|
|
return undefined;
|
|
}
|
|
},
|
|
listDirContents,
|
|
createFileOrFolder,
|
|
uploadFile,
|
|
renameFileOrFolder,
|
|
updateFile,
|
|
moveFileOrFolder,
|
|
deleteFileOrFolder,
|
|
downloadFile,
|
|
},
|
|
};
|
|
|
|
type GeneralFunctions = {
|
|
/**
|
|
* INTERNAL: Gets the general settings of a server.
|
|
* @param serverId - The ID of the server.
|
|
*/
|
|
get: (serverId: string) => Promise<General>;
|
|
|
|
/**
|
|
* Updates the name of the server.
|
|
* @param newName - The new name for the server.
|
|
*/
|
|
updateName: (newName: string) => Promise<void>;
|
|
|
|
/**
|
|
* Sends a power action to the server.
|
|
|
|
* @param action - The power action to send (e.g., "start", "stop", "restart").
|
|
*/
|
|
power: (action: string) => Promise<void>;
|
|
|
|
/**
|
|
* Reinstalls the server with the specified project and version.
|
|
* @param loader - Whether to use a loader.
|
|
* @param projectId - The ID of the project.
|
|
* @param versionId - Optional version ID.
|
|
* @param loaderVersionId - Optional loader version ID.
|
|
* @param hardReset - Whether to perform a hard reset.
|
|
*/
|
|
reinstall: (
|
|
serverId: string,
|
|
loader: boolean,
|
|
projectId: string,
|
|
versionId?: string,
|
|
loaderVersionId?: string,
|
|
hardReset?: boolean,
|
|
) => Promise<void>;
|
|
|
|
/**
|
|
* Reinstalls the server from a mrpack.
|
|
* @param mrpack - The mrpack file.
|
|
* @param hardReset - Whether to perform a hard reset.
|
|
*/
|
|
reinstallFromMrpack: (mrpack: File, hardReset?: boolean) => Promise<void>;
|
|
|
|
/**
|
|
* Suspends or resumes the server.
|
|
* @param status - True to suspend the server, false to resume.
|
|
*/
|
|
suspend: (status: boolean) => Promise<void>;
|
|
|
|
/**
|
|
* INTERNAL: Gets the general settings of a server.
|
|
*/
|
|
getMotd: () => Promise<string>;
|
|
|
|
/**
|
|
* INTERNAL: Updates the general settings of a server.
|
|
* @param motd - The new motd.
|
|
*/
|
|
setMotd: (motd: string) => Promise<void>;
|
|
|
|
/**
|
|
* INTERNAL: Gets the config file of a server.
|
|
* @param fileName - The name of the file.
|
|
*/
|
|
fetchConfigFile: (fileName: string) => Promise<any>;
|
|
};
|
|
|
|
type ModFunctions = {
|
|
/**
|
|
* INTERNAL: Gets the mods of a server.
|
|
* @param serverId - The ID of the server.
|
|
* @returns
|
|
*/
|
|
get: (serverId: string) => Promise<Mod[]>;
|
|
|
|
/**
|
|
* Installs a mod to a server.
|
|
* @param projectId - The ID of the project.
|
|
* @param versionId - The ID of the version.
|
|
*/
|
|
install: (projectId: string, versionId: string) => Promise<void>;
|
|
|
|
/**
|
|
* Removes a mod from a server.
|
|
* @param modId - The ID of the mod.
|
|
*/
|
|
remove: (modId: string) => Promise<void>;
|
|
|
|
/**
|
|
* Reinstalls a mod to a server.
|
|
* @param modId - The ID of the mod.
|
|
* @param versionId - The ID of the version.
|
|
*/
|
|
reinstall: (modId: string, versionId: string) => Promise<void>;
|
|
};
|
|
|
|
type BackupFunctions = {
|
|
/**
|
|
* INTERNAL: Gets the backups of a server.
|
|
* @param serverId - The ID of the server.
|
|
* @returns
|
|
*/
|
|
get: (serverId: string) => Promise<Backup[]>;
|
|
|
|
/**
|
|
* Creates a new backup for the server.
|
|
* @param backupName - The name of the backup.
|
|
*/
|
|
create: (backupName: string) => Promise<void>;
|
|
|
|
/**
|
|
* Renames a backup for the server.
|
|
* @param backupId - The ID of the backup.
|
|
* @param newName - The new name for the backup.
|
|
*/
|
|
rename: (backupId: string, newName: string) => Promise<void>;
|
|
|
|
/**
|
|
* Deletes a backup for the server.
|
|
* @param backupId - The ID of the backup.
|
|
*/
|
|
delete: (backupId: string) => Promise<void>;
|
|
|
|
/**
|
|
* Restores a backup for the server.
|
|
* @param serverId - The ID of the server.
|
|
* @param backupId - The ID of the backup.
|
|
*/
|
|
restore: (backupId: string) => Promise<void>;
|
|
|
|
/**
|
|
* Downloads a backup for the server.
|
|
* @param backupId - The ID of the backup.
|
|
*/
|
|
download: (backupId: string) => Promise<void>;
|
|
|
|
/**
|
|
* Updates the auto backup settings of the server.
|
|
* @param autoBackup - Whether to enable auto backup.
|
|
* @param interval - The interval to backup at (in Hours).
|
|
*/
|
|
updateAutoBackup: (autoBackup: "enable" | "disable", interval: number) => Promise<void>;
|
|
|
|
/**
|
|
* Gets the auto backup settings of the server.
|
|
*/
|
|
getAutoBackup: () => Promise<AutoBackupSettings>;
|
|
};
|
|
|
|
type NetworkFunctions = {
|
|
/**
|
|
* INTERNAL: Gets the network settings of a server.
|
|
* @param serverId - The ID of the server.
|
|
* @returns
|
|
*/
|
|
get: (serverId: string) => Promise<Allocation[]>;
|
|
|
|
/**
|
|
* Reserves a new allocation for the server.
|
|
* @param name - The name of the allocation.
|
|
* @returns The allocated network port details.
|
|
*/
|
|
reserveAllocation: (name: string) => Promise<Allocation>;
|
|
|
|
/**
|
|
* Updates the allocation for the server.
|
|
* @param port - The port to update.
|
|
* @param name - The new name for the allocation.
|
|
*/
|
|
updateAllocation: (port: number, name: string) => Promise<void>;
|
|
|
|
/**
|
|
* Deletes an allocation for the server.
|
|
* @param port - The port to delete.
|
|
*/
|
|
deleteAllocation: (port: number) => Promise<void>;
|
|
|
|
/**
|
|
* Checks if a subdomain is available.
|
|
* @param subdomain - The subdomain to check.
|
|
* @returns True if the subdomain is available, otherwise false.
|
|
*/
|
|
checkSubdomainAvailability: (subdomain: string) => Promise<boolean>;
|
|
|
|
/**
|
|
* Changes the subdomain of the server.
|
|
* @param subdomain - The new subdomain.
|
|
*/
|
|
changeSubdomain: (subdomain: string) => Promise<void>;
|
|
};
|
|
|
|
type StartupFunctions = {
|
|
/**
|
|
* INTERNAL: Gets the startup settings of a server.
|
|
* @param serverId - The ID of the server.
|
|
* @returns
|
|
*/
|
|
get: (serverId: string) => Promise<Startup>;
|
|
|
|
/**
|
|
* Updates the startup settings of a server.
|
|
* @param invocation - The invocation of the server.
|
|
* @param jdkVersion - The version of the JDK.
|
|
* @param jdkBuild - The build of the JDK.
|
|
*/
|
|
update: (
|
|
invocation: string,
|
|
jdkVersion: "lts8" | "lts11" | "lts17" | "lts21",
|
|
jdkBuild: "corretto" | "temurin" | "graal",
|
|
) => Promise<void>;
|
|
};
|
|
|
|
type FSFunctions = {
|
|
/**
|
|
* INTERNAL: Gets the file system settings of a server.
|
|
* @param serverId
|
|
* @returns
|
|
*/
|
|
get: (serverId: string) => Promise<JWTAuth>;
|
|
|
|
/**
|
|
* @param path - The path to list the contents of.
|
|
* @param page - The page to list.
|
|
* @param pageSize - The page size to list.
|
|
* @returns
|
|
*/
|
|
listDirContents: (path: string, page: number, pageSize: number) => Promise<any>;
|
|
|
|
/**
|
|
* @param path - The path to create the file or folder at.
|
|
* @param type - The type of file or folder to create.
|
|
* @returns
|
|
*/
|
|
createFileOrFolder: (path: string, type: "file" | "directory") => Promise<any>;
|
|
|
|
/**
|
|
* @param path - The path to upload the file to.
|
|
* @param file - The file to upload.
|
|
* @returns
|
|
*/
|
|
uploadFile: (path: string, file: File) => Promise<any>;
|
|
|
|
/**
|
|
* @param path - The path to rename the file or folder at.
|
|
* @param name - The new name for the file or folder.
|
|
* @returns
|
|
*/
|
|
renameFileOrFolder: (path: string, name: string) => Promise<any>;
|
|
|
|
/**
|
|
* @param path - The path to update the file at.
|
|
* @param content - The new content for the file.
|
|
* @returns
|
|
*/
|
|
updateFile: (path: string, content: string) => Promise<any>;
|
|
|
|
/**
|
|
* @param path - The path to move the file or folder at.
|
|
* @param newPath - The new path for the file or folder.
|
|
* @returns
|
|
*/
|
|
moveFileOrFolder: (path: string, newPath: string) => Promise<any>;
|
|
|
|
/**
|
|
* @param path - The path to delete the file or folder at.
|
|
* @param recursive - Whether to delete the file or folder recursively.
|
|
* @returns
|
|
*/
|
|
deleteFileOrFolder: (path: string, recursive: boolean) => Promise<any>;
|
|
|
|
/**
|
|
* @param serverId - The ID of the server.
|
|
* @param path - The path to download the file from.
|
|
* @param raw - Whether to return the raw blob.
|
|
* @returns
|
|
*/
|
|
downloadFile: (path: string, raw?: boolean) => Promise<any>;
|
|
};
|
|
|
|
type GeneralModule = General & GeneralFunctions;
|
|
type ModsModule = { data: Mod[] } & ModFunctions;
|
|
type BackupsModule = { data: Backup[] } & BackupFunctions;
|
|
type NetworkModule = { allocations: Allocation[] } & NetworkFunctions;
|
|
type StartupModule = Startup & StartupFunctions;
|
|
type FSModule = { auth: JWTAuth } & FSFunctions;
|
|
|
|
type ModulesMap = {
|
|
general: GeneralModule;
|
|
mods: ModsModule;
|
|
backups: BackupsModule;
|
|
network: NetworkModule;
|
|
startup: StartupModule;
|
|
ws: JWTAuth;
|
|
fs: FSModule;
|
|
};
|
|
|
|
type avaliableModules = ("general" | "mods" | "backups" | "network" | "startup" | "ws" | "fs")[];
|
|
|
|
export type Server<T extends avaliableModules> = {
|
|
[K in T[number]]?: ModulesMap[K];
|
|
} & {
|
|
/**
|
|
* Refreshes the included modules of the server
|
|
* @param refreshModules - The modules to refresh.
|
|
*/
|
|
refresh: (refreshModules?: avaliableModules) => Promise<void>;
|
|
setError: (error: Error) => void;
|
|
error?: Error;
|
|
serverId: string;
|
|
};
|
|
|
|
export const usePyroServer = async (serverId: string, includedModules: avaliableModules) => {
|
|
const server: Server<typeof includedModules> = reactive({
|
|
refresh: async (refreshModules?: avaliableModules) => {
|
|
const promises: Promise<void>[] = [];
|
|
if (refreshModules) {
|
|
for (const module of refreshModules) {
|
|
promises.push(
|
|
(async () => {
|
|
const mods = modules[module];
|
|
if (mods.get) {
|
|
const data = await mods.get(serverId);
|
|
server[module] = { ...server[module], ...data };
|
|
}
|
|
})(),
|
|
);
|
|
}
|
|
} else {
|
|
for (const module of includedModules) {
|
|
promises.push(
|
|
(async () => {
|
|
const mods = modules[module];
|
|
if (mods.get) {
|
|
const data = await mods.get(serverId);
|
|
server[module] = { ...server[module], ...data };
|
|
}
|
|
})(),
|
|
);
|
|
}
|
|
}
|
|
await Promise.all(promises);
|
|
},
|
|
setError: (error: Error) => {
|
|
server.error = error;
|
|
},
|
|
serverId,
|
|
});
|
|
|
|
for (const module of includedModules) {
|
|
const mods = modules[module];
|
|
server[module] = mods;
|
|
}
|
|
|
|
internalServerRefrence.value = server;
|
|
|
|
await server.refresh();
|
|
|
|
return server as Server<typeof includedModules>;
|
|
};
|