aboutsummaryrefslogtreecommitdiff
path: root/server/frontend/src
diff options
context:
space:
mode:
Diffstat (limited to 'server/frontend/src')
-rw-r--r--server/frontend/src/Analysis.tsx84
-rw-r--r--server/frontend/src/App.tsx17
-rw-r--r--server/frontend/src/api-schema.d.ts288
-rw-r--r--server/frontend/src/api.ts7
-rw-r--r--server/frontend/src/index.tsx2
5 files changed, 319 insertions, 79 deletions
diff --git a/server/frontend/src/Analysis.tsx b/server/frontend/src/Analysis.tsx
new file mode 100644
index 0000000..3bf9c13
--- /dev/null
+++ b/server/frontend/src/Analysis.tsx
@@ -0,0 +1,84 @@
+import { createAsync, useParams } from "@solidjs/router"
+import { client, getAnalysisList, paths } from "./api.ts";
+import { createSignal, For, onMount, Show, Suspense } from "solid-js";
+import { SolidApexCharts } from "solid-apexcharts";
+
+type AnalysisResult =
+ { status: 'not requested' }
+ | { status: 'loading' }
+ | { status: 'loaded', result: paths['/analysis/execute']['get']['responses'][200]['content']['application/json'] }
+
+export default function Analysis() {
+ const pathParams = useParams();
+ const analysisId = pathParams.id!;
+ let analysis = createAsync(() => getAnalysisList());
+ const analysisName = () => analysis()?.data?.find(it => it.id == analysisId)?.name
+ const [startTimestamp, setStartTimestamp] = createSignal(new Date().getTime() - 1000 * 60 * 60 * 24 * 30);
+ const [endTimestamp, setEndTimestamp] = createSignal(new Date().getTime());
+ const [analysisResult, setAnalysisResult] = createSignal<AnalysisResult>({ status: 'not requested' });
+ return <>
+ <h1><Suspense fallback="Name not loaded...">{analysisName()}</Suspense></h1>
+ <p>
+ <label>
+ Start:
+ <input type="date" value={new Date(startTimestamp()).toISOString().substring(0, 10)} onInput={it => setStartTimestamp(it.target.valueAsNumber)}></input>
+ </label>
+ <label>
+ End:
+ <input type="date" value={new Date(endTimestamp()).toISOString().substring(0, 10)} onInput={it => setEndTimestamp(it.target.valueAsNumber)}></input>
+ </label>
+ <button disabled={analysisResult().status === 'loading'} onClick={() => {
+ setAnalysisResult({ status: 'loading' });
+ (async () => {
+ const result = await client.GET('/analysis/execute', {
+ params: {
+ query: {
+ analysis: analysisId,
+ tEnd: endTimestamp(),
+ tStart: startTimestamp()
+ }
+ }
+ });
+ setAnalysisResult({
+ status: "loaded",
+ result: result.data!
+ });
+ })();
+ }}>
+ Refresh
+ </button>
+
+ <Show when={takeIf(analysisResult(), it => it.status == 'loaded')}>
+ {element =>
+ <For each={element().result.visualizations}>
+ {item =>
+ <div>
+ <SolidApexCharts
+ width={1200}
+ type="bar"
+ options={{
+ xaxis: {
+ type: 'numeric'
+ }
+ }}
+ series={[
+ {
+ name: item.label,
+ data: item.dataPoints.map(it => ([it.time, it.value]))
+ }
+ ]}
+ ></SolidApexCharts>
+ </div>
+ }
+ </For>}
+ </Show>
+ </p >
+ </>
+}
+
+function takeIf<T extends P, P>(
+ obj: P,
+ condition: (arg: P) => arg is T,
+): T | false {
+ return condition(obj) ? obj : false;
+} \ No newline at end of file
diff --git a/server/frontend/src/App.tsx b/server/frontend/src/App.tsx
index e35bb42..bdc1007 100644
--- a/server/frontend/src/App.tsx
+++ b/server/frontend/src/App.tsx
@@ -1,11 +1,20 @@
-import type { Component } from "solid-js";
-import { A } from "@solidjs/router";
+import { For, Suspense, type Component } from "solid-js";
+import { A, createAsync } from "@solidjs/router";
+import { client, getAnalysisList } from "./api.ts";
const App: Component = () => {
+ let analysis = createAsync(() => getAnalysisList());
return (
<>
- Hello World
- <A href="/test">Test Page</A>
+ <Suspense fallback="Loading analysis...">
+ <ul>
+ <For each={analysis()?.data}>
+ {item =>
+ <li><A href={`/analysis/${item.id}`}>{item.name}</A></li>
+ }
+ </For>
+ </ul>
+ </Suspense>
</>
);
};
diff --git a/server/frontend/src/api-schema.d.ts b/server/frontend/src/api-schema.d.ts
index 93bedc9..7ba1db4 100644
--- a/server/frontend/src/api-schema.d.ts
+++ b/server/frontend/src/api-schema.d.ts
@@ -4,95 +4,233 @@
*/
export interface paths {
- "/profiles": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
+ "/profiles": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** List all profiles and players known to ledger */
+ get: operations["listProfiles"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/analysis/execute": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Execute an analysis on a given timeframe */
+ get: operations["executeAnalysis"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/analysis/list": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** List all installed analysis */
+ get: operations["getAnalysis"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/item": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Get item names for item ids */
+ get: operations["getItemNames"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
};
- /** List all profiles and players known to ledger */
- get: operations["listProfiles"];
- put?: never;
- post?: never;
- delete?: never;
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
- };
- "/item": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
+ "/entries": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Get all log entries */
+ get: operations["getLogEntries"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
};
- /** Get item names for item ids */
- get: operations["getItemNames"];
- put?: never;
- post?: never;
- delete?: never;
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
- };
}
export type webhooks = Record<string, never>;
export interface components {
- schemas: never;
- responses: never;
- parameters: never;
- requestBodies: never;
- headers: never;
- pathItems: never;
+ schemas: never;
+ responses: never;
+ parameters: never;
+ requestBodies: never;
+ headers: never;
+ pathItems: never;
}
export type $defs = Record<string, never>;
export interface operations {
- listProfiles: {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
+ listProfiles: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ playerId: string;
+ profileId: string;
+ }[];
+ };
+ };
+ };
};
- requestBody?: never;
- responses: {
- 200: {
- headers: {
- [name: string]: unknown;
+ executeAnalysis: {
+ parameters: {
+ query: {
+ /** @description An analysis id obtained from getAnalysis */
+ analysis: string;
+ /** @description The start of the timeframe to analyze */
+ tStart: number;
+ /** @description The end of the timeframe to analyze. Make sure to use the end of the day if you want the entire day included. */
+ tEnd: number;
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- content: {
- "application/json": {
- playerId: string;
- profileId: string;
- }[];
+ requestBody?: never;
+ responses: {
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ visualizations: {
+ label: string;
+ xLabel: string;
+ yLabel: string;
+ dataPoints: {
+ time: number;
+ value: number;
+ }[];
+ }[];
+ };
+ };
+ };
};
- };
};
- };
- getItemNames: {
- parameters: {
- query?: {
- itemId?: string[];
- };
- header?: never;
- path?: never;
- cookie?: never;
+ getAnalysis: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ name: string;
+ id: string;
+ }[];
+ };
+ };
+ };
+ };
+ getItemNames: {
+ parameters: {
+ query: {
+ itemId: string[];
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ [key: string]: string;
+ };
+ };
+ };
+ };
};
- requestBody?: never;
- responses: {
- 200: {
- headers: {
- [name: string]: unknown;
+ getLogEntries: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- content: {
- "application/json": {
- [key: string]: string;
- };
+ requestBody?: never;
+ responses: {
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ /** @enum {string} */
+ type: "ACCESSORIES_SWAPPING" | "ALLOWANCE_GAIN" | "AUCTION_BOUGHT" | "AUCTION_LISTING_CHARGE" | "AUCTION_SOLD" | "AUTOMERCHANT_PROFIT_COLLECT" | "BANK_DEPOSIT" | "BANK_INTEREST" | "BANK_WITHDRAW" | "BASIC_REFORGE" | "BAZAAR_BUY_INSTANT" | "BAZAAR_BUY_ORDER" | "BAZAAR_SELL_INSTANT" | "BAZAAR_SELL_ORDER" | "BITS_PURSE_STATUS" | "BOOSTER_COOKIE_ATE" | "CAPSAICIN_EYEDROPS_USED" | "COMMUNITY_SHOP_BUY" | "CORPSE_DESECRATED" | "DIE_ROLLED" | "DRACONIC_SACRIFICE" | "DUNGEON_CHEST_OPEN" | "FORGED" | "GOD_POTION_DRANK" | "GOD_POTION_MIXIN_DRANK" | "GUMMY_POLAR_BEAR_ATE" | "KAT_TIMESKIP" | "KAT_UPGRADE" | "KISMET_REROLL" | "KUUDRA_CHEST_OPEN" | "NPC_BUY" | "NPC_SELL" | "PEST_REPELLENT_USED" | "VISITOR_BARGAIN" | "WYRM_EVOKED";
+ id: string;
+ items: {
+ itemId: string;
+ /** @enum {string} */
+ direction: "GAINED" | "TRANSFORM" | "SYNC" | "CATALYST" | "LOST";
+ amount: number;
+ }[];
+ }[];
+ };
+ };
};
- };
};
- };
}
diff --git a/server/frontend/src/api.ts b/server/frontend/src/api.ts
index 22cf6ca..8ab6272 100644
--- a/server/frontend/src/api.ts
+++ b/server/frontend/src/api.ts
@@ -1,6 +1,13 @@
import createClient from "openapi-fetch";
import type { paths } from "./api-schema.js";
+import { query } from "@solidjs/router";
+export { type paths };
const apiRoot = import.meta.env.DEV ? "//localhost:8080/api" : "/api";
export const client = createClient<paths>({ baseUrl: apiRoot });
+
+export const getAnalysisList = query(
+ () => client.GET("/analysis/list"),
+ "getAnalysisList"
+)
diff --git a/server/frontend/src/index.tsx b/server/frontend/src/index.tsx
index 6ab7d4e..610a78b 100644
--- a/server/frontend/src/index.tsx
+++ b/server/frontend/src/index.tsx
@@ -1,5 +1,6 @@
/* @refresh reload */
import { render } from "solid-js/web";
+import 'solid-devtools';
import "./index.css";
import type { RouteDefinition } from "@solidjs/router";
@@ -16,6 +17,7 @@ if (!(root instanceof HTMLElement)) {
const routes: Array<RouteDefinition> = [
{ path: "/", component: lazy(() => import("./App.tsx")) },
{ path: "/test/", component: lazy(() => import("./Test.tsx")) },
+ { path: "/analysis/:id", component: lazy(() => import("./Analysis.tsx")) },
];
render(() => <Router>{routes}</Router>, root!);