diff --git a/litelist-api/dub.json b/litelist-api/dub.json index 875c33a..500f397 100644 --- a/litelist-api/dub.json +++ b/litelist-api/dub.json @@ -9,6 +9,7 @@ "d2sqlite3": "~>1.0.0", "handy-httpd": "~>7.9.3", "jwt": "~>0.4.0", + "resusage": "~>0.3.2", "slf4d": "~>2.4.2" }, "description": "API for the litelist application.", diff --git a/litelist-api/dub.selections.json b/litelist-api/dub.selections.json index 4ffd16e..46ed182 100644 --- a/litelist-api/dub.selections.json +++ b/litelist-api/dub.selections.json @@ -9,6 +9,7 @@ "httparsed": "1.2.1", "jwt": "0.4.0", "memutils": "1.0.9", + "resusage": "0.3.2", "slf4d": "2.4.2", "streams": "3.5.0" } diff --git a/litelist-api/source/app.d b/litelist-api/source/app.d index f8eb7f5..158bdc0 100644 --- a/litelist-api/source/app.d +++ b/litelist-api/source/app.d @@ -57,9 +57,7 @@ private HttpServer initServer() { immutable string API_PATH = "/api"; auto mainHandler = new PathDelegatingHandler(); - mainHandler.addMapping(Method.GET, API_PATH ~ "/status", (ref HttpRequestContext ctx) { - ctx.response.writeBodyString("online"); - }); + mainHandler.addMapping(Method.GET, API_PATH ~ "/status", &handleStatus); auto optionsHandler = toHandler((ref HttpRequestContext ctx) { ctx.response.setStatus(HttpStatus.OK); @@ -82,3 +80,16 @@ private HttpServer initServer() { return new HttpServer(mainHandler, config); } + +void handleStatus(ref HttpRequestContext ctx) { + import resusage; + import std.process; + import std.json; + + immutable int pId = thisProcessID(); + ProcessMemInfo procInfo = processMemInfo(pId); + JSONValue data = JSONValue(string[string].init); + data.object["virtualMemory"] = JSONValue(procInfo.usedVirtMem); + data.object["physicalMemory"] = JSONValue(procInfo.usedRAM); + ctx.response.writeBodyString(data.toString(), "application/json"); +} diff --git a/litelist-app/src/api/base.ts b/litelist-app/src/api/base.ts index baa7b99..c86ab36 100644 --- a/litelist-app/src/api/base.ts +++ b/litelist-app/src/api/base.ts @@ -1 +1,20 @@ export const API_URL = import.meta.env.VITE_API_URL + +export interface StatusInfo { + virtualMemory: number + physicalMemory: number +} + +export async function getStatus(): Promise { + try { + const response = await fetch(API_URL + "/status") + if (response.ok) { + return await response.json() + } + console.warn("Non-OK status response: ", response.status) + return null + } catch (error: any) { + console.error(error) + return null + } +} diff --git a/litelist-app/src/components/PageContainer.vue b/litelist-app/src/components/PageContainer.vue index 075052a..9de1de3 100644 --- a/litelist-app/src/components/PageContainer.vue +++ b/litelist-app/src/components/PageContainer.vue @@ -3,11 +3,37 @@ This is a generic container to wrap around a page's content to keep it within a mobile-friendly width. --> diff --git a/litelist-app/src/util.ts b/litelist-app/src/util.ts index 986703c..e6d5855 100644 --- a/litelist-app/src/util.ts +++ b/litelist-app/src/util.ts @@ -25,3 +25,35 @@ export function getSecondsTilExpire(token: string): number { const decoded = parseJwt(token) return decoded.exp - now } + +/** + * Format bytes as human-readable text. + * + * @param bytes Number of bytes. + * @param si True to use metric (SI) units, aka powers of 1000. False to use + * binary (IEC), aka powers of 1024. + * @param dp Number of decimal places to display. + * + * @return Formatted string. + */ +export function humanFileSize(bytes, si=false, dp=1) { + const thresh = si ? 1000 : 1024; + + if (Math.abs(bytes) < thresh) { + return bytes + ' B'; + } + + const units = si + ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; + let u = -1; + const r = 10**dp; + + do { + bytes /= thresh; + ++u; + } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1); + + + return bytes.toFixed(dp) + ' ' + units[u]; +}