diff options
Diffstat (limited to 'server')
30 files changed, 3333 insertions, 21 deletions
diff --git a/server/aio/build.gradle.kts b/server/aio/build.gradle.kts new file mode 100644 index 0000000..22819f0 --- /dev/null +++ b/server/aio/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + kotlin("jvm") + application +} + +dependencies { + declareKtorVersion() + implementation(project(":server:core")) + implementation(project(":server:frontend")) +} + +java { + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) +} + +application { + mainClass.set("moe.nea.ledger.server.core.ApplicationKt") +} + diff --git a/server/aio/src/main/kotlin/moe/nea/ledger/server/aio/AIO.kt b/server/aio/src/main/kotlin/moe/nea/ledger/server/aio/AIO.kt new file mode 100644 index 0000000..e59301f --- /dev/null +++ b/server/aio/src/main/kotlin/moe/nea/ledger/server/aio/AIO.kt @@ -0,0 +1,20 @@ +package moe.nea.ledger.server.aio + +import io.ktor.server.application.Application +import io.ktor.server.http.content.singlePageApplication +import io.ktor.server.routing.Routing +import moe.nea.ledger.server.core.AIOProvider + + +class AIO : AIOProvider { + override fun Routing.installExtraRouting() { + singlePageApplication { + useResources = true + filesPath = "ledger-web-dist" + defaultPage = "index.html" + } + } + + override fun Application.module() { + } +}
\ No newline at end of file diff --git a/server/analysis/build.gradle.kts b/server/analysis/build.gradle.kts new file mode 100644 index 0000000..d5d48a0 --- /dev/null +++ b/server/analysis/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + kotlin("jvm") + kotlin("plugin.serialization") + id("com.google.devtools.ksp") +} + +dependencies { + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.8.0") + ksp("dev.zacsweers.autoservice:auto-service-ksp:1.2.0") + implementation("com.google.auto.service:auto-service-annotations:1.1.1") + implementation(project(":database:impl")) +} + +java { + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) +} diff --git a/server/analysis/src/main/kotlin/moe/nea/ledger/analysis/Analysis.kt b/server/analysis/src/main/kotlin/moe/nea/ledger/analysis/Analysis.kt new file mode 100644 index 0000000..abcf8ed --- /dev/null +++ b/server/analysis/src/main/kotlin/moe/nea/ledger/analysis/Analysis.kt @@ -0,0 +1,9 @@ +package moe.nea.ledger.analysis + +import java.sql.Connection + +interface Analysis { + val id: String + val name: String + fun perform(database: Connection, filter: AnalysisFilter): AnalysisResult +}
\ No newline at end of file diff --git a/server/analysis/src/main/kotlin/moe/nea/ledger/analysis/AnalysisFilter.kt b/server/analysis/src/main/kotlin/moe/nea/ledger/analysis/AnalysisFilter.kt new file mode 100644 index 0000000..10d9b9c --- /dev/null +++ b/server/analysis/src/main/kotlin/moe/nea/ledger/analysis/AnalysisFilter.kt @@ -0,0 +1,26 @@ +package moe.nea.ledger.analysis + +import moe.nea.ledger.database.DBLogEntry +import moe.nea.ledger.database.Query +import moe.nea.ledger.database.columns.DBUlid +import moe.nea.ledger.database.sql.Clause +import moe.nea.ledger.utils.ULIDWrapper +import java.time.Instant +import java.time.ZoneId +import java.util.UUID + +interface AnalysisFilter { + fun applyTo(query: Query) { + query.where(Clause { column(DBLogEntry.transactionId) ge value(DBUlid, ULIDWrapper.lowerBound(startWindow)) }) + .where(Clause { column(DBLogEntry.transactionId) le value(DBUlid, ULIDWrapper.upperBound(endWindow)) }) +//TODO: .where(Clause { column(DBLogEntry.profileId) inList profiles }) + } + + fun timeZone(): ZoneId { + return ZoneId.systemDefault() + } + + val startWindow: Instant + val endWindow: Instant + val profiles: List<UUID> +} diff --git a/server/analysis/src/main/kotlin/moe/nea/ledger/analysis/AnalysisResult.kt b/server/analysis/src/main/kotlin/moe/nea/ledger/analysis/AnalysisResult.kt new file mode 100644 index 0000000..4ad47f7 --- /dev/null +++ b/server/analysis/src/main/kotlin/moe/nea/ledger/analysis/AnalysisResult.kt @@ -0,0 +1,8 @@ +package moe.nea.ledger.analysis + +import kotlinx.serialization.Serializable + +@Serializable +data class AnalysisResult( + val visualizations: List<Visualization> +)
\ No newline at end of file diff --git a/server/analysis/src/main/kotlin/moe/nea/ledger/analysis/CoinsSpentOnAuctions.kt b/server/analysis/src/main/kotlin/moe/nea/ledger/analysis/CoinsSpentOnAuctions.kt new file mode 100644 index 0000000..d1ce52b --- /dev/null +++ b/server/analysis/src/main/kotlin/moe/nea/ledger/analysis/CoinsSpentOnAuctions.kt @@ -0,0 +1,49 @@ +package moe.nea.ledger.analysis + +import com.google.auto.service.AutoService +import moe.nea.ledger.ItemChange +import moe.nea.ledger.ItemId +import moe.nea.ledger.TransactionType +import moe.nea.ledger.database.DBItemEntry +import moe.nea.ledger.database.DBLogEntry +import moe.nea.ledger.database.sql.Clause +import java.sql.Connection +import java.time.LocalDate + +@AutoService(Analysis::class) +class CoinsSpentOnAuctions : Analysis { + override val name: String + get() = "Shopping Costs" + override val id: String + get() = "coins-spent-on-auctions" + + override fun perform(database: Connection, filter: AnalysisFilter): AnalysisResult { + val query = DBLogEntry.from(database) + .join(DBItemEntry, Clause { column(DBItemEntry.transactionId) eq column(DBLogEntry.transactionId) }) + .where(Clause { column(DBItemEntry.itemId) eq ItemId.COINS }) + .where(Clause { column(DBItemEntry.mode) eq ItemChange.ChangeDirection.LOST }) + .where(Clause { column(DBLogEntry.type) eq TransactionType.AUCTION_BOUGHT }) + .select(DBItemEntry.size, DBLogEntry.transactionId) + filter.applyTo(query) + val spentThatDay = mutableMapOf<LocalDate, Double>() + for (resultRow in query) { + val timestamp = resultRow[DBLogEntry.transactionId].getTimestamp() + val damage = resultRow[DBItemEntry.size] + val localZone = filter.timeZone() + val localDate = timestamp.atZone(localZone).toLocalDate() + spentThatDay.merge(localDate, damage) { a, b -> a + b } + } + return AnalysisResult( + listOf( + Visualization( + "Coins spent on auctions", + xLabel = "Time", + yLabel = "Coins Spent that day", + dataPoints = spentThatDay.entries.map { (k, v) -> + DataPoint(k.atTime(12, 0).atZone(filter.timeZone()).toInstant(), v) + } + ) + ) + ) + } +}
\ No newline at end of file diff --git a/server/analysis/src/main/kotlin/moe/nea/ledger/analysis/Visualization.kt b/server/analysis/src/main/kotlin/moe/nea/ledger/analysis/Visualization.kt new file mode 100644 index 0000000..d0c0d56 --- /dev/null +++ b/server/analysis/src/main/kotlin/moe/nea/ledger/analysis/Visualization.kt @@ -0,0 +1,30 @@ +package moe.nea.ledger.analysis + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import java.time.Instant + +@Serializable +data class Visualization( + val label: String, + val xLabel: String, + val yLabel: String, + val dataPoints: List<DataPoint> +) + +@Serializable +data class DataPoint( + val time: @Serializable(InstantSerializer::class) Instant, + val value: Double, +) + +object InstantSerializer : KSerializer<Instant> { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("java.time.Instant", PrimitiveKind.LONG) + override fun serialize(encoder: Encoder, value: Instant) = encoder.encodeLong(value.toEpochMilli()) + override fun deserialize(decoder: Decoder): Instant = Instant.ofEpochMilli(decoder.decodeLong()) +}
\ No newline at end of file diff --git a/server/core/build.gradle.kts b/server/core/build.gradle.kts index 87f613a..b2a3222 100644 --- a/server/core/build.gradle.kts +++ b/server/core/build.gradle.kts @@ -2,19 +2,22 @@ plugins { kotlin("jvm") kotlin("plugin.serialization") application + id("com.github.gmazzo.buildconfig") } -val ktor_version = "3.0.3" dependencies { - implementation(platform("io.ktor:ktor-bom:$ktor_version")) - implementation("io.ktor:ktor-server-netty") - implementation("io.ktor:ktor-server-status-pages") - implementation("io.ktor:ktor-server-content-negotiation") - implementation("io.ktor:ktor-server-openapi") - implementation("io.ktor:ktor-serialization-kotlinx-json") - implementation("io.ktor:ktor-server-compression") - implementation(project(":database:impl")) + declareKtorVersion() + api("io.ktor:ktor-server-netty") + api("io.ktor:ktor-server-status-pages") + api("io.ktor:ktor-server-content-negotiation") + api("io.ktor:ktor-serialization-kotlinx-json") + api("io.ktor:ktor-server-compression") + api("io.ktor:ktor-server-cors") + api("sh.ondr:kotlin-json-schema:0.1.1") + api(project(":server:analysis")) + api(project(":database:impl")) + api(project(":server:swagger")) runtimeOnly("ch.qos.logback:logback-classic:1.5.16") runtimeOnly("org.xerial:sqlite-jdbc:3.45.3.0") @@ -29,3 +32,6 @@ application { "-Dledger.databasefolder=${project(":mod").file("run/money-ledger").absoluteFile}") mainClass.set("moe.nea.ledger.server.core.ApplicationKt") } +buildConfig { + packageName("moe.nea.ledger.gen") +} diff --git a/server/core/src/main/kotlin/moe/nea/ledger/server/core/Application.kt b/server/core/src/main/kotlin/moe/nea/ledger/server/core/Application.kt index 0ea6ed3..23b2a6a 100644 --- a/server/core/src/main/kotlin/moe/nea/ledger/server/core/Application.kt +++ b/server/core/src/main/kotlin/moe/nea/ledger/server/core/Application.kt @@ -6,29 +6,76 @@ import io.ktor.server.application.install import io.ktor.server.netty.EngineMain import io.ktor.server.plugins.compression.Compression import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.ktor.server.plugins.cors.routing.CORS +import io.ktor.server.response.respondRedirect +import io.ktor.server.routing.Routing +import io.ktor.server.routing.get import io.ktor.server.routing.route import io.ktor.server.routing.routing +import kotlinx.serialization.json.Json import moe.nea.ledger.database.Database +import moe.nea.ledger.gen.BuildConfig +import moe.nea.ledger.server.core.api.Documentation +import moe.nea.ledger.server.core.api.Info +import moe.nea.ledger.server.core.api.Server import moe.nea.ledger.server.core.api.apiRouting +import moe.nea.ledger.server.core.api.openApiDocsJson +import moe.nea.ledger.server.core.api.openApiUi +import moe.nea.ledger.server.core.api.setApiRoot import java.io.File fun main(args: Array<String>) { EngineMain.main(args) } +interface AIOProvider { + fun Routing.installExtraRouting() + fun Application.module() +} fun Application.module() { + val aio = runCatching { + Class.forName("moe.nea.ledger.server.aio.AIO") + .newInstance() as AIOProvider + }.getOrNull() + aio?.run { module() } install(Compression) + install(Documentation) { + info = Info( + "Ledger Analysis Server", + "Your local API for loading ledger data", + BuildConfig.VERSION + ) + servers.add( + Server("http://localhost:8080/api", "Your Local Server") + ) + } install(ContentNegotiation) { - json() + json(Json { + this.explicitNulls = false + this.encodeDefaults = true + }) // cbor() } - val database = Database(File(System.getProperty("ledger.databasefolder"))) + install(CORS) { + anyHost() + } + val database = Database(File(System.getProperty("ledger.databasefolder", + "/home/nea/.local/share/PrismLauncher/instances/Skyblock/.minecraft/money-ledger"))) database.loadAndUpgrade() routing { route("/api") { - this.apiRouting(database) + setApiRoot() + get { call.respondRedirect("/openapi/") } + apiRouting(database) + } + route("/api.json") { + openApiDocsJson() + } + route("/openapi") { + openApiUi("/api.json") } + aio?.run { installExtraRouting() } } } diff --git a/server/core/src/main/kotlin/moe/nea/ledger/server/core/api/BaseApi.kt b/server/core/src/main/kotlin/moe/nea/ledger/server/core/api/BaseApi.kt index 264f74b..3240a65 100644 --- a/server/core/src/main/kotlin/moe/nea/ledger/server/core/api/BaseApi.kt +++ b/server/core/src/main/kotlin/moe/nea/ledger/server/core/api/BaseApi.kt @@ -1,18 +1,49 @@ package moe.nea.ledger.server.core.api +import io.ktor.http.Url +import io.ktor.http.toURI import io.ktor.server.response.respond -import io.ktor.server.response.respondText import io.ktor.server.routing.Route -import io.ktor.server.routing.Routing import io.ktor.server.routing.get +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import kotlinx.coroutines.withContext +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromStream +import moe.nea.ledger.ItemChange +import moe.nea.ledger.TransactionType +import moe.nea.ledger.analysis.Analysis +import moe.nea.ledger.analysis.AnalysisFilter +import moe.nea.ledger.analysis.AnalysisResult +import moe.nea.ledger.database.DBItemEntry import moe.nea.ledger.database.DBLogEntry import moe.nea.ledger.database.Database +import moe.nea.ledger.database.sql.Clause import moe.nea.ledger.server.core.Profile +import moe.nea.ledger.utils.ULIDWrapper +import java.time.Instant +import java.util.ServiceLoader +import java.util.UUID fun Route.apiRouting(database: Database) { - get("/") { - call.respondText("K") + val allOfferedAnalysisServices: Map<String, Analysis> = run { + val serviceLoader = ServiceLoader.load(Analysis::class.java, environment.classLoader) + val map = mutableMapOf<String, Analysis>() + serviceLoader.forEach { + map[it.id] = it + } + map } + get("/profiles") { val profiles = DBLogEntry.from(database.connection) .select(DBLogEntry.playerId, DBLogEntry.profileId) @@ -21,5 +52,154 @@ fun Route.apiRouting(database: Database) { Profile(it[DBLogEntry.playerId], it[DBLogEntry.profileId]) } call.respond(profiles) + }.docs { + summary = "List all profiles and players known to ledger" + operationId = "listProfiles" + tag(Tags.PROFILE) + respondsOk { + schema<List<Profile>>() + } + } + @OptIn(DelicateCoroutinesApi::class) + val itemNames = GlobalScope.async { + val itemNamesUrl = + Url("https://github.com/nea89o/ledger-auxiliary-data/raw/refs/heads/master/data/item_names.json") + Json.decodeFromStream<Map<String, String>>(itemNamesUrl.toURI().toURL().openStream()) + } + get("/analysis/execute") { + val analysis = allOfferedAnalysisServices[call.queryParameters["analysis"]] ?: TODO() + val start = call.queryParameters["tStart"]?.toLongOrNull()?.let { Instant.ofEpochMilli(it) } ?: TODO() + val end = call.queryParameters["tEnd"]?.toLongOrNull()?.let { Instant.ofEpochMilli(it) } ?: TODO() + val analysisResult = withContext(Dispatchers.IO) { + analysis.perform( + database.connection, + object : AnalysisFilter { + override val startWindow: Instant + get() = start + override val endWindow: Instant + get() = end + override val profiles: List<UUID> + get() = listOf() + } + ) + } + call.respond(analysisResult) + }.docs { + summary = "Execute an analysis on a given timeframe" + operationId = "executeAnalysis" + queryParameter<String>("analysis", description = "An analysis id obtained from getAnalysis") + queryParameter<Long>("tStart", description = "The start of the timeframe to analyze") + queryParameter<Long>("tEnd", + description = "The end of the timeframe to analyze. Make sure to use the end of the day if you want the entire day included.") + tag(Tags.DATA) + respondsOk { + schema<AnalysisResult>() + } + } + get("/analysis/list") { + call.respond(allOfferedAnalysisServices.values.map { + AnalysisListing(it.name, it.id) + }) + }.docs { + summary = "List all installed analysis" + operationId = "getAnalysis" + tag(Tags.DATA) + respondsOk { + schema<List<AnalysisListing>>() + } + } + get("/item") { + val itemIds = call.queryParameters.getAll("itemId")?.toSet() ?: emptySet() + val itemNameMap = itemNames.await() + call.respond(itemIds.associateWith { itemNameMap[it] }) + }.docs { + summary = "Get item names for item ids" + operationId = "getItemNames" + tag(Tags.HYPIXEL) + queryParameter<List<String>>("itemId") + respondsOk { + schema<Map<String, String?>>() + } + } + get("/entries") { + val logs = mutableMapOf<ULIDWrapper, LogEntry>() + val items = mutableMapOf<ULIDWrapper, MutableList<SerializableItemChange>>() + withContext(Dispatchers.IO) { + DBLogEntry.from(database.connection) + .join(DBItemEntry, Clause { column(DBItemEntry.transactionId) eq column(DBLogEntry.transactionId) }) + .select(DBLogEntry.profileId, + DBLogEntry.playerId, + DBLogEntry.transactionId, + DBLogEntry.type, + DBItemEntry.mode, + DBItemEntry.itemId, + DBItemEntry.size) + .forEach { row -> + logs.getOrPut(row[DBLogEntry.transactionId]) { + LogEntry(row[DBLogEntry.type], + row[DBLogEntry.transactionId], + listOf()) + } + items.getOrPut(row[DBLogEntry.transactionId]) { mutableListOf() } + .add(SerializableItemChange( + row[DBItemEntry.itemId].string, + row[DBItemEntry.mode], + row[DBItemEntry.size], + )) + } + } + val compiled = logs.values.map { it.copy(items = items[it.id]!!) } + call.respond(compiled) + }.docs { + summary = "Get all log entries" + operationId = "getLogEntries" + tag(Tags.DATA) + respondsOk { + schema<List<LogEntry>>() + } + } +} + +@Serializable +data class AnalysisListing( + val name: String, + val id: String, +) + +@Serializable +data class LogEntry( + val type: TransactionType, + val id: @Serializable(ULIDSerializer::class) ULIDWrapper, + val items: List<SerializableItemChange>, +) + +@Serializable +data class SerializableItemChange( + val itemId: String, + val direction: ItemChange.ChangeDirection, + val amount: Double, +) + +object ULIDSerializer : KSerializer<ULIDWrapper> { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ULID", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): ULIDWrapper { + return ULIDWrapper(decoder.decodeString()) + } + + override fun serialize(encoder: Encoder, value: ULIDWrapper) { + encoder.encodeString(value.wrapped) } } + +enum class Tags : IntoTag { + PROFILE, + HYPIXEL, + MANAGEMENT, + DATA, + ; + + override fun intoTag(): String { + return name + } +}
\ No newline at end of file diff --git a/server/core/test-requests/test-hello.http b/server/core/test-requests/test-hello.http deleted file mode 100644 index 3ddf352..0000000 --- a/server/core/test-requests/test-hello.http +++ /dev/null @@ -1,5 +0,0 @@ -### GET request to example server -GET localhost:8080/ - -### GET profiles -GET localhost:8080/api/profiles
\ No newline at end of file diff --git a/server/frontend/.gitignore b/server/frontend/.gitignore new file mode 100644 index 0000000..76add87 --- /dev/null +++ b/server/frontend/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist
\ No newline at end of file diff --git a/server/frontend/build.gradle.kts b/server/frontend/build.gradle.kts new file mode 100644 index 0000000..72fe6ea --- /dev/null +++ b/server/frontend/build.gradle.kts @@ -0,0 +1,17 @@ +import com.github.gradle.node.pnpm.task.PnpmTask + +plugins { + id("com.github.node-gradle.node") version "7.1.0" + `java-library` +} + +val webDist by tasks.register("webDist", PnpmTask::class) { + dependsOn(tasks.pnpmInstall) + args.addAll("build") + outputs.dir("dist") +} +tasks.jar { + from(webDist) { + into("ledger-web-dist/") + } +} diff --git a/server/frontend/index.html b/server/frontend/index.html new file mode 100644 index 0000000..48c59fc --- /dev/null +++ b/server/frontend/index.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <meta name="theme-color" content="#000000" /> + <link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.ico" /> + <title>Solid App</title> + </head> + <body> + <noscript>You need to enable JavaScript to run this app.</noscript> + <div id="root"></div> + + <script src="/src/index.tsx" type="module"></script> + </body> +</html> diff --git a/server/frontend/package.json b/server/frontend/package.json new file mode 100644 index 0000000..a8a8880 --- /dev/null +++ b/server/frontend/package.json @@ -0,0 +1,37 @@ +{ + "name": "ledger-frontend", + "version": "0.0.0", + "description": "", + "type": "module", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "test:ts": "tsc --noEmit", + "genApi": "openapi-typescript http://localhost:8080/api.json -o src/api-schema.d.ts" + }, + "license": "MIT", + "devDependencies": { + "openapi-typescript": "^7.5.2", + "solid-devtools": "^0.33.0", + "typescript": "^5.7.2", + "vite": "^6.0.0", + "vite-plugin-solid": "^2.11.0" + }, + "dependencies": { + "@solidjs/router": "^0.15.3", + "apexcharts": "^4.3.0", + "moment": "^2.30.1", + "openapi-fetch": "^0.13.4", + "solid-apexcharts": "^0.4.0", + "solid-js": "^1.9.3" + }, + "devEngines": { + "packageManager": { + "name": "pnpm", + "onFail": "error" + } + }, + "packageManager": "pnpm@^9.3.0" +} diff --git a/server/frontend/pnpm-lock.yaml b/server/frontend/pnpm-lock.yaml new file mode 100644 index 0000000..6483404 --- /dev/null +++ b/server/frontend/pnpm-lock.yaml @@ -0,0 +1,1920 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@solidjs/router': + specifier: ^0.15.3 + version: 0.15.3(solid-js@1.9.4) + apexcharts: + specifier: ^4.3.0 + version: 4.3.0 + moment: + specifier: ^2.30.1 + version: 2.30.1 + openapi-fetch: + specifier: ^0.13.4 + version: 0.13.4 + solid-apexcharts: + specifier: ^0.4.0 + version: 0.4.0(apexcharts@4.3.0)(solid-js@1.9.4) + solid-js: + specifier: ^1.9.3 + version: 1.9.4 + devDependencies: + openapi-typescript: + specifier: ^7.5.2 + version: 7.5.2(typescript@5.7.3) + solid-devtools: + specifier: ^0.33.0 + version: 0.33.0(solid-js@1.9.4)(vite@6.0.7(sass@1.83.4)) + typescript: + specifier: ^5.7.2 + version: 5.7.3 + vite: + specifier: ^6.0.0 + version: 6.0.7(sass@1.83.4) + vite-plugin-solid: + specifier: ^2.11.0 + version: 2.11.0(solid-js@1.9.4)(vite@6.0.7(sass@1.83.4)) + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.26.5': + resolution: {integrity: sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.26.0': + resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.26.5': + resolution: {integrity: sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.26.5': + resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.18.6': + resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.26.5': + resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.26.0': + resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.26.5': + resolution: {integrity: sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-jsx@7.25.9': + resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.25.9': + resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.25.9': + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.26.5': + resolution: {integrity: sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.26.5': + resolution: {integrity: sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.24.2': + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.24.2': + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.24.2': + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.24.2': + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.24.2': + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.24.2': + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.24.2': + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.24.2': + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.24.2': + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.24.2': + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.24.2': + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.24.2': + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.24.2': + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.24.2': + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.24.2': + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.24.2': + resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.24.2': + resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.24.2': + resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.24.2': + resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.24.2': + resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.24.2': + resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.24.2': + resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.24.2': + resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.24.2': + resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.24.2': + resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@nothing-but/utils@0.17.0': + resolution: {integrity: sha512-TuCHcHLOqDL0SnaAxACfuRHBNRgNJcNn9X0GiH5H3YSDBVquCr3qEIG3FOQAuMyZCbu9w8nk2CHhOsn7IvhIwQ==} + + '@parcel/watcher-android-arm64@2.5.0': + resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.0': + resolution: {integrity: sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.0': + resolution: {integrity: sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.0': + resolution: {integrity: sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.0': + resolution: {integrity: sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm-musl@2.5.0': + resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.5.0': + resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.5.0': + resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.5.0': + resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.5.0': + resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-win32-arm64@2.5.0': + resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.0': + resolution: {integrity: sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.0': + resolution: {integrity: sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.0': + resolution: {integrity: sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==} + engines: {node: '>= 10.0.0'} + + '@redocly/ajv@8.11.2': + resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==} + + '@redocly/config@0.20.1': + resolution: {integrity: sha512-TYiTDtuItiv95YMsrRxyCs1HKLrDPtTvpaD3+kDKXBnFDeJuYKZ+eHXpCr6YeN4inxfVBs7DLhHsQcs9srddyQ==} + + '@redocly/openapi-core@1.27.2': + resolution: {integrity: sha512-qVrDc27DHpeO2NRCMeRdb4299nijKQE3BY0wrA+WUHlOLScorIi/y7JzammLk22IaTvjR9Mv9aTAdjE1aUwJnA==} + engines: {node: '>=14.19.0', npm: '>=7.0.0'} + + '@rollup/rollup-android-arm-eabi@4.30.1': + resolution: {integrity: sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.30.1': + resolution: {integrity: sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.30.1': + resolution: {integrity: sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.30.1': + resolution: {integrity: sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.30.1': + resolution: {integrity: sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.30.1': + resolution: {integrity: sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.30.1': + resolution: {integrity: sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.30.1': + resolution: {integrity: sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.30.1': + resolution: {integrity: sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.30.1': + resolution: {integrity: sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.30.1': + resolution: {integrity: sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.30.1': + resolution: {integrity: sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.30.1': + resolution: {integrity: sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.30.1': + resolution: {integrity: sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.30.1': + resolution: {integrity: sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.30.1': + resolution: {integrity: sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.30.1': + resolution: {integrity: sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.30.1': + resolution: {integrity: sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.30.1': + resolution: {integrity: sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==} + cpu: [x64] + os: [win32] + + '@solid-devtools/debugger@0.26.0': + resolution: {integrity: sha512-36QxZ+s/lY60E+Pb9q0eTsdqgaog4c823WIj5dC2LFdGrGXbVGBQEj6k7CgvMnEETdwndrd0Fm72fQyYPlZrVA==} + peerDependencies: + solid-js: ^1.9.0 + + '@solid-devtools/shared@0.19.0': + resolution: {integrity: sha512-OGo6l84f9X5YEAqSEM4Xl94+xKXSqmACMzKWsAqO0BStLBMVL0vIVu286AQk5XkNxn11/EB9wrdkZc9GUzKlxA==} + peerDependencies: + solid-js: ^1.9.0 + + '@solid-primitives/bounds@0.0.122': + resolution: {integrity: sha512-kUq/IprOdFr/rg2upon5lQGOoTnDAmxQS4ASKK2l+VwoKSctdPwgu/4qJxEITZikL+nB0myYZzBZWptySV0cRg==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/cursor@0.0.115': + resolution: {integrity: sha512-8nEmUN/sacXPChwuJOAi6Yi6VnxthW/Jk8VGvvcF38AenjUvOA6FHI6AkJILuFXjQw1PGxia1YbH/Mn77dPiOA==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/event-listener@2.3.3': + resolution: {integrity: sha512-DAJbl+F0wrFW2xmcV8dKMBhk9QLVLuBSW+TR4JmIfTaObxd13PuL7nqaXnaYKDWOYa6otB00qcCUIGbuIhSUgQ==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/keyboard@1.2.8': + resolution: {integrity: sha512-pJtcbkjozS6L1xvTht9rPpyPpX55nAkfBzbFWdf3y0Suwh6qClTibvvObzKOf7uzQ+8aZRDH4LsoGmbTKXtJjQ==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/media@2.2.10': + resolution: {integrity: sha512-zICx9lXvevycyHmzUp1AfrxmUsF27JGvDygf51mHUpvy/Y2SmxkM6UHKstBDlRSpLUhPTnF0iHCfdfne6g4Fow==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/platform@0.1.2': + resolution: {integrity: sha512-sSxcZfuUrtxcwV0vdjmGnZQcflACzMfLriVeIIWXKp8hzaS3Or3tO6EFQkTd3L8T5dTq+kTtLvPscXIpL0Wzdg==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/refs@1.0.8': + resolution: {integrity: sha512-+jIsWG8/nYvhaCoG2Vg6CJOLgTmPKFbaCrNQKWfChalgUf9WrVxWw0CdJb3yX15n5lUcQ0jBo6qYtuVVmBLpBw==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/resize-observer@2.0.27': + resolution: {integrity: sha512-RmusjHqoA4U6MKI/T9yBJVDttASHpWBki1+YwM9zGXEDBqbysTa3lZpnlB244LzphQmobgeXVS78v0KtXVsF9g==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/rootless@1.4.5': + resolution: {integrity: sha512-GFJE9GC3ojx0aUKqAUZmQPyU8fOVMtnVNrkdk2yS4kd17WqVSpXpoTmo9CnOwA+PG7FTzdIkogvfLQSLs4lrww==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/scheduled@1.4.4': + resolution: {integrity: sha512-BTGdFP7t+s7RSak+s1u0eTix4lHP23MrbGkgQTFlt1E+4fmnD/bEx3ZfNW7Grylz3GXgKyXrgDKA7jQ/wuWKgA==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/static-store@0.0.8': + resolution: {integrity: sha512-ZecE4BqY0oBk0YG00nzaAWO5Mjcny8Fc06CdbXadH9T9lzq/9GefqcSe/5AtdXqjvY/DtJ5C6CkcjPZO0o/eqg==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/static-store@0.0.9': + resolution: {integrity: sha512-8zaTXTEnQFqdwfkqWmGVb/OYgSTbRgxJSWQNfLuA+KnuW4RzTRQE2jzgnNJjJjaloruv9EHGvikmJzQJ5aOrEw==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/styles@0.0.114': + resolution: {integrity: sha512-SFXr16mgr6LvZAIj6L7i59HHg+prAmIF8VP/U3C6jSHz68Eh1G71vaWr9vlJVpy/j6bh1N8QUzu5CgtvIC92OQ==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/utils@6.2.3': + resolution: {integrity: sha512-CqAwKb2T5Vi72+rhebSsqNZ9o67buYRdEJrIFzRXz3U59QqezuuxPsyzTSVCacwS5Pf109VRsgCJQoxKRoECZQ==} + peerDependencies: + solid-js: ^1.6.12 + + '@solidjs/router@0.15.3': + resolution: {integrity: sha512-iEbW8UKok2Oio7o6Y4VTzLj+KFCmQPGEpm1fS3xixwFBdclFVBvaQVeibl1jys4cujfAK5Kn6+uG2uBm3lxOMw==} + peerDependencies: + solid-js: ^1.8.6 + + '@svgdotjs/svg.draggable.js@3.0.5': + resolution: {integrity: sha512-ljL/fB0tAjRfFOJGhXpr7rEx9DJ6D7Pxt3AXvgxjEM17g6wK3Ho9nXhntraOMx8JLZdq4NBMjokeXMvnQzJVYA==} + peerDependencies: + '@svgdotjs/svg.js': ^3.2.4 + + '@svgdotjs/svg.filter.js@3.0.8': + resolution: {integrity: sha512-YshF2YDaeRA2StyzAs5nUPrev7npQ38oWD0eTRwnsciSL2KrRPMoUw8BzjIXItb3+dccKGTX3IQOd2NFzmHkog==} + engines: {node: '>= 0.8.0'} + + '@svgdotjs/svg.js@3.2.4': + resolution: {integrity: sha512-BjJ/7vWNowlX3Z8O4ywT58DqbNRyYlkk6Yz/D13aB7hGmfQTvGX4Tkgtm/ApYlu9M7lCQi15xUEidqMUmdMYwg==} + + '@svgdotjs/svg.resize.js@2.0.5': + resolution: {integrity: sha512-4heRW4B1QrJeENfi7326lUPYBCevj78FJs8kfeDxn5st0IYPIRXoTtOSYvTzFWgaWWXd3YCDE6ao4fmv91RthA==} + engines: {node: '>= 14.18'} + peerDependencies: + '@svgdotjs/svg.js': ^3.2.4 + '@svgdotjs/svg.select.js': ^4.0.1 + + '@svgdotjs/svg.select.js@4.0.2': + resolution: {integrity: sha512-5gWdrvoQX3keo03SCmgaBbD+kFftq0F/f2bzCbNnpkkvW6tk4rl4MakORzFuNjvXPWwB4az9GwuvVxQVnjaK2g==} + engines: {node: '>= 14.18'} + peerDependencies: + '@svgdotjs/svg.js': ^3.2.4 + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.6': + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@yr/monotone-cubic-spline@1.0.3': + resolution: {integrity: sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==} + + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + apexcharts@4.3.0: + resolution: {integrity: sha512-PfvZQpv91T68hzry9l5zP3Gip7sQvF0nFK91uCBrswIKX7rbIdbVNS4fOks9m9yP3Ppgs6LHgU2M/mjoG4NM0A==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + babel-plugin-jsx-dom-expressions@0.39.5: + resolution: {integrity: sha512-dwyVkszHRsZCXfFusu3xq1DJS7twhgLrjEpMC1gtTfJG1xSrMMKWWhdl1SFFFNXrvYDsoHiRxSbku/TzLxHNxg==} + peerDependencies: + '@babel/core': ^7.20.12 + + babel-preset-solid@1.9.3: + resolution: {integrity: sha512-jvlx5wDp8s+bEF9sGFw/84SInXOA51ttkUEroQziKMbxplXThVKt83qB6bDTa1HuLNatdU9FHpFOiQWs1tLQIg==} + peerDependencies: + '@babel/core': ^7.0.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + caniuse-lite@1.0.30001692: + resolution: {integrity: sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==} + + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + + electron-to-chromium@1.5.83: + resolution: {integrity: sha512-LcUDPqSt+V0QmI47XLzZrz5OqILSMGsPFkDYus22rIbgorSvBYEFqq854ltTmUdHkY92FSdAAvsh4jWEULMdfQ==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + esbuild@0.24.2: + resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + html-entities@2.3.3: + resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + immutable@5.0.3: + resolution: {integrity: sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==} + + index-to-position@0.1.2: + resolution: {integrity: sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==} + engines: {node: '>=18'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + + js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + merge-anything@5.1.7: + resolution: {integrity: sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==} + engines: {node: '>=12.13'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + openapi-fetch@0.13.4: + resolution: {integrity: sha512-JHX7UYjLEiHuQGCPxa3CCCIqe/nc4bTIF9c4UYVC8BegAbWoS3g4gJxKX5XcG7UtYQs2060kY6DH64KkvNZahg==} + + openapi-typescript-helpers@0.0.15: + resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==} + + openapi-typescript@7.5.2: + resolution: {integrity: sha512-W/QXuQz0Fa3bGY6LKoqTCgrSX+xI/ST+E5RXo2WBmp3WwgXCWKDJPHv5GZmElF4yLCccnqYsakBDOJikHZYGRw==} + hasBin: true + peerDependencies: + typescript: ^5.x + + parse-json@8.1.0: + resolution: {integrity: sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==} + engines: {node: '>=18'} + + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + postcss@8.5.1: + resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} + engines: {node: ^10 || ^12 || >=14} + + readdirp@4.1.1: + resolution: {integrity: sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==} + engines: {node: '>= 14.18.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + rollup@4.30.1: + resolution: {integrity: sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + sass@1.83.4: + resolution: {integrity: sha512-B1bozCeNQiOgDcLd33e2Cs2U60wZwjUUXzh900ZyQF5qUasvMdDZYbQ566LJu7cqR+sAHlAfO6RMkaID5s6qpA==} + engines: {node: '>=14.0.0'} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + seroval-plugins@1.2.0: + resolution: {integrity: sha512-hULTbfzSe81jGWLH8TAJjkEvw6JWMqOo9Uq+4V4vg+HNq53hyHldM9ZOfjdzokcFysiTp9aFdV2vJpZFqKeDjQ==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval@1.2.0: + resolution: {integrity: sha512-GURoU99ko2UiAgUC3qDCk59Jb3Ss4Po8VIMGkG8j5PFo2Q7y0YSMP8QG9NuL/fJCoTz9V1XZUbpNIMXPOfaGpA==} + engines: {node: '>=10'} + + solid-apexcharts@0.4.0: + resolution: {integrity: sha512-b3tjFaYNF2ggvFq+VSaxxj2ocxfKKkB7jnWL60uDaokv2A3RwFXXzmPYZMkN80FQaLnxeNzkUdre/S9HgshLnA==} + engines: {node: '>=18', pnpm: '>=8.6.0'} + peerDependencies: + apexcharts: ^4.0.0 + solid-js: ^1.6.0 + + solid-devtools@0.33.0: + resolution: {integrity: sha512-xRB4Jhgns3dBuM/s0j70BpXKy77sNjISud9xXBv60qC4cnJ/TcuVHI1t+05luj1BEKJVQSokqIaVoZWcjqA9yw==} + peerDependencies: + solid-js: ^1.9.0 + vite: ^2.2.3 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + vite: + optional: true + + solid-js@1.9.4: + resolution: {integrity: sha512-ipQl8FJ31bFUoBNScDQTG3BjN6+9Rg+Q+f10bUbnO6EOTTf5NGerJeHc7wyu5I4RMHEl/WwZwUmy/PTRgxxZ8g==} + + solid-refresh@0.6.3: + resolution: {integrity: sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==} + peerDependencies: + solid-js: ^1.3 + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + supports-color@9.4.0: + resolution: {integrity: sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==} + engines: {node: '>=12'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + type-fest@4.32.0: + resolution: {integrity: sha512-rfgpoi08xagF3JSdtJlCwMq9DGNDE0IMh3Mkpc1wUypg9vPi786AiqeBBKcqvIkq42azsBM85N490fyZjeUftw==} + engines: {node: '>=16'} + + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + engines: {node: '>=14.17'} + hasBin: true + + update-browserslist-db@1.1.2: + resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js-replace@1.0.1: + resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==} + + validate-html-nesting@1.2.2: + resolution: {integrity: sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg==} + + vite-plugin-solid@2.11.0: + resolution: {integrity: sha512-G+NiwDj4EAeUE0wt3Ur9f+Lt9oMUuLd0FIxYuqwJSqRacKQRteCwUFzNy8zMEt88xWokngQhiFjfJMhjc1fDXw==} + peerDependencies: + '@testing-library/jest-dom': ^5.16.6 || ^5.17.0 || ^6.* + solid-js: ^1.7.2 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + '@testing-library/jest-dom': + optional: true + + vite@6.0.7: + resolution: {integrity: sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.0.5: + resolution: {integrity: sha512-h4Vflt9gxODPFNGPwp4zAMZRpZR7eslzwH2c5hn5kNZ5rhnKyRJ50U+yGCdc2IRaBs8O4haIgLNGrV5CrpMsCA==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + vite: + optional: true + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml-ast-parser@0.0.43: + resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.26.5': {} + + '@babel/core@7.26.0': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.5 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helpers': 7.26.0 + '@babel/parser': 7.26.5 + '@babel/template': 7.25.9 + '@babel/traverse': 7.26.5 + '@babel/types': 7.26.5 + convert-source-map: 2.0.0 + debug: 4.4.0(supports-color@9.4.0) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.26.5': + dependencies: + '@babel/parser': 7.26.5 + '@babel/types': 7.26.5 + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.26.5': + dependencies: + '@babel/compat-data': 7.26.5 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.4 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-module-imports@7.18.6': + dependencies: + '@babel/types': 7.26.5 + + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.26.5 + '@babel/types': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.26.5': {} + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helpers@7.26.0': + dependencies: + '@babel/template': 7.25.9 + '@babel/types': 7.26.5 + + '@babel/parser@7.26.5': + dependencies: + '@babel/types': 7.26.5 + + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/template@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.5 + '@babel/types': 7.26.5 + + '@babel/traverse@7.26.5': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.5 + '@babel/parser': 7.26.5 + '@babel/template': 7.25.9 + '@babel/types': 7.26.5 + debug: 4.4.0(supports-color@9.4.0) + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.26.5': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@esbuild/aix-ppc64@0.24.2': + optional: true + + '@esbuild/android-arm64@0.24.2': + optional: true + + '@esbuild/android-arm@0.24.2': + optional: true + + '@esbuild/android-x64@0.24.2': + optional: true + + '@esbuild/darwin-arm64@0.24.2': + optional: true + + '@esbuild/darwin-x64@0.24.2': + optional: true + + '@esbuild/freebsd-arm64@0.24.2': + optional: true + + '@esbuild/freebsd-x64@0.24.2': + optional: true + + '@esbuild/linux-arm64@0.24.2': + optional: true + + '@esbuild/linux-arm@0.24.2': + optional: true + + '@esbuild/linux-ia32@0.24.2': + optional: true + + '@esbuild/linux-loong64@0.24.2': + optional: true + + '@esbuild/linux-mips64el@0.24.2': + optional: true + + '@esbuild/linux-ppc64@0.24.2': + optional: true + + '@esbuild/linux-riscv64@0.24.2': + optional: true + + '@esbuild/linux-s390x@0.24.2': + optional: true + + '@esbuild/linux-x64@0.24.2': + optional: true + + '@esbuild/netbsd-arm64@0.24.2': + optional: true + + '@esbuild/netbsd-x64@0.24.2': + optional: true + + '@esbuild/openbsd-arm64@0.24.2': + optional: true + + '@esbuild/openbsd-x64@0.24.2': + optional: true + + '@esbuild/sunos-x64@0.24.2': + optional: true + + '@esbuild/win32-arm64@0.24.2': + optional: true + + '@esbuild/win32-ia32@0.24.2': + optional: true + + '@esbuild/win32-x64@0.24.2': + optional: true + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@nothing-but/utils@0.17.0': {} + + '@parcel/watcher-android-arm64@2.5.0': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.0': + optional: true + + '@parcel/watcher-darwin-x64@2.5.0': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.0': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.0': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.0': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.0': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.0': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.0': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.0': + optional: true + + '@parcel/watcher-win32-arm64@2.5.0': + optional: true + + '@parcel/watcher-win32-ia32@2.5.0': + optional: true + + '@parcel/watcher-win32-x64@2.5.0': + optional: true + + '@parcel/watcher@2.5.0': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.0 + '@parcel/watcher-darwin-arm64': 2.5.0 + '@parcel/watcher-darwin-x64': 2.5.0 + '@parcel/watcher-freebsd-x64': 2.5.0 + '@parcel/watcher-linux-arm-glibc': 2.5.0 + '@parcel/watcher-linux-arm-musl': 2.5.0 + '@parcel/watcher-linux-arm64-glibc': 2.5.0 + '@parcel/watcher-linux-arm64-musl': 2.5.0 + '@parcel/watcher-linux-x64-glibc': 2.5.0 + '@parcel/watcher-linux-x64-musl': 2.5.0 + '@parcel/watcher-win32-arm64': 2.5.0 + '@parcel/watcher-win32-ia32': 2.5.0 + '@parcel/watcher-win32-x64': 2.5.0 + optional: true + + '@redocly/ajv@8.11.2': + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js-replace: 1.0.1 + + '@redocly/config@0.20.1': {} + + '@redocly/openapi-core@1.27.2(supports-color@9.4.0)': + dependencies: + '@redocly/ajv': 8.11.2 + '@redocly/config': 0.20.1 + colorette: 1.4.0 + https-proxy-agent: 7.0.6(supports-color@9.4.0) + js-levenshtein: 1.1.6 + js-yaml: 4.1.0 + minimatch: 5.1.6 + node-fetch: 2.7.0 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - encoding + - supports-color + + '@rollup/rollup-android-arm-eabi@4.30.1': + optional: true + + '@rollup/rollup-android-arm64@4.30.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.30.1': + optional: true + + '@rollup/rollup-darwin-x64@4.30.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.30.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.30.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.30.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.30.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.30.1': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.30.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.30.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.30.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.30.1': + optional: true + + '@solid-devtools/debugger@0.26.0(solid-js@1.9.4)': + dependencies: + '@nothing-but/utils': 0.17.0 + '@solid-devtools/shared': 0.19.0(solid-js@1.9.4) + '@solid-primitives/bounds': 0.0.122(solid-js@1.9.4) + '@solid-primitives/cursor': 0.0.115(solid-js@1.9.4) + '@solid-primitives/event-listener': 2.3.3(solid-js@1.9.4) + '@solid-primitives/keyboard': 1.2.8(solid-js@1.9.4) + '@solid-primitives/platform': 0.1.2(solid-js@1.9.4) + '@solid-primitives/rootless': 1.4.5(solid-js@1.9.4) + '@solid-primitives/scheduled': 1.4.4(solid-js@1.9.4) + '@solid-primitives/static-store': 0.0.8(solid-js@1.9.4) + '@solid-primitives/utils': 6.2.3(solid-js@1.9.4) + solid-js: 1.9.4 + + '@solid-devtools/shared@0.19.0(solid-js@1.9.4)': + dependencies: + '@nothing-but/utils': 0.17.0 + '@solid-primitives/event-listener': 2.3.3(solid-js@1.9.4) + '@solid-primitives/media': 2.2.10(solid-js@1.9.4) + '@solid-primitives/refs': 1.0.8(solid-js@1.9.4) + '@solid-primitives/rootless': 1.4.5(solid-js@1.9.4) + '@solid-primitives/scheduled': 1.4.4(solid-js@1.9.4) + '@solid-primitives/static-store': 0.0.8(solid-js@1.9.4) + '@solid-primitives/styles': 0.0.114(solid-js@1.9.4) + '@solid-primitives/utils': 6.2.3(solid-js@1.9.4) + solid-js: 1.9.4 + + '@solid-primitives/bounds@0.0.122(solid-js@1.9.4)': + dependencies: + '@solid-primitives/event-listener': 2.3.3(solid-js@1.9.4) + '@solid-primitives/resize-observer': 2.0.27(solid-js@1.9.4) + '@solid-primitives/static-store': 0.0.8(solid-js@1.9.4) + '@solid-primitives/utils': 6.2.3(solid-js@1.9.4) + solid-js: 1.9.4 + + '@solid-primitives/cursor@0.0.115(solid-js@1.9.4)': + dependencies: + '@solid-primitives/utils': 6.2.3(solid-js@1.9.4) + solid-js: 1.9.4 + + '@solid-primitives/event-listener@2.3.3(solid-js@1.9.4)': + dependencies: + '@solid-primitives/utils': 6.2.3(solid-js@1.9.4) + solid-js: 1.9.4 + + '@solid-primitives/keyboard@1.2.8(solid-js@1.9.4)': + dependencies: + '@solid-primitives/event-listener': 2.3.3(solid-js@1.9.4) + '@solid-primitives/rootless': 1.4.5(solid-js@1.9.4) + '@solid-primitives/utils': 6.2.3(solid-js@1.9.4) + solid-js: 1.9.4 + + '@solid-primitives/media@2.2.10(solid-js@1.9.4)': + dependencies: + '@solid-primitives/event-listener': 2.3.3(solid-js@1.9.4) + '@solid-primitives/rootless': 1.4.5(solid-js@1.9.4) + '@solid-primitives/static-store': 0.0.9(solid-js@1.9.4) + '@solid-primitives/utils': 6.2.3(solid-js@1.9.4) + solid-js: 1.9.4 + + '@solid-primitives/platform@0.1.2(solid-js@1.9.4)': + dependencies: + solid-js: 1.9.4 + + '@solid-primitives/refs@1.0.8(solid-js@1.9.4)': + dependencies: + '@solid-primitives/utils': 6.2.3(solid-js@1.9.4) + solid-js: 1.9.4 + + '@solid-primitives/resize-observer@2.0.27(solid-js@1.9.4)': + dependencies: + '@solid-primitives/event-listener': 2.3.3(solid-js@1.9.4) + '@solid-primitives/rootless': 1.4.5(solid-js@1.9.4) + '@solid-primitives/static-store': 0.0.9(solid-js@1.9.4) + '@solid-primitives/utils': 6.2.3(solid-js@1.9.4) + solid-js: 1.9.4 + + '@solid-primitives/rootless@1.4.5(solid-js@1.9.4)': + dependencies: + '@solid-primitives/utils': 6.2.3(solid-js@1.9.4) + solid-js: 1.9.4 + + '@solid-primitives/scheduled@1.4.4(solid-js@1.9.4)': + dependencies: + solid-js: 1.9.4 + + '@solid-primitives/static-store@0.0.8(solid-js@1.9.4)': + dependencies: + '@solid-primitives/utils': 6.2.3(solid-js@1.9.4) + solid-js: 1.9.4 + + '@solid-primitives/static-store@0.0.9(solid-js@1.9.4)': + dependencies: + '@solid-primitives/utils': 6.2.3(solid-js@1.9.4) + solid-js: 1.9.4 + + '@solid-primitives/styles@0.0.114(solid-js@1.9.4)': + dependencies: + '@solid-primitives/rootless': 1.4.5(solid-js@1.9.4) + '@solid-primitives/utils': 6.2.3(solid-js@1.9.4) + solid-js: 1.9.4 + + '@solid-primitives/utils@6.2.3(solid-js@1.9.4)': + dependencies: + solid-js: 1.9.4 + + '@solidjs/router@0.15.3(solid-js@1.9.4)': + dependencies: + solid-js: 1.9.4 + + '@svgdotjs/svg.draggable.js@3.0.5(@svgdotjs/svg.js@3.2.4)': + dependencies: + '@svgdotjs/svg.js': 3.2.4 + + '@svgdotjs/svg.filter.js@3.0.8': + dependencies: + '@svgdotjs/svg.js': 3.2.4 + + '@svgdotjs/svg.js@3.2.4': {} + + '@svgdotjs/svg.resize.js@2.0.5(@svgdotjs/svg.js@3.2.4)(@svgdotjs/svg.select.js@4.0.2(@svgdotjs/svg.js@3.2.4))': + dependencies: + '@svgdotjs/svg.js': 3.2.4 + '@svgdotjs/svg.select.js': 4.0.2(@svgdotjs/svg.js@3.2.4) + + '@svgdotjs/svg.select.js@4.0.2(@svgdotjs/svg.js@3.2.4)': + dependencies: + '@svgdotjs/svg.js': 3.2.4 + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.26.5 + '@babel/types': 7.26.5 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 + + '@types/babel__generator@7.6.8': + dependencies: + '@babel/types': 7.26.5 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.26.5 + '@babel/types': 7.26.5 + + '@types/babel__traverse@7.20.6': + dependencies: + '@babel/types': 7.26.5 + + '@types/estree@1.0.6': {} + + '@yr/monotone-cubic-spline@1.0.3': {} + + agent-base@7.1.3: {} + + ansi-colors@4.1.3: {} + + apexcharts@4.3.0: + dependencies: + '@svgdotjs/svg.draggable.js': 3.0.5(@svgdotjs/svg.js@3.2.4) + '@svgdotjs/svg.filter.js': 3.0.8 + '@svgdotjs/svg.js': 3.2.4 + '@svgdotjs/svg.resize.js': 2.0.5(@svgdotjs/svg.js@3.2.4)(@svgdotjs/svg.select.js@4.0.2(@svgdotjs/svg.js@3.2.4)) + '@svgdotjs/svg.select.js': 4.0.2(@svgdotjs/svg.js@3.2.4) + '@yr/monotone-cubic-spline': 1.0.3 + + argparse@2.0.1: {} + + babel-plugin-jsx-dom-expressions@0.39.5(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.18.6 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/types': 7.26.5 + html-entities: 2.3.3 + parse5: 7.2.1 + validate-html-nesting: 1.2.2 + + babel-preset-solid@1.9.3(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + babel-plugin-jsx-dom-expressions: 0.39.5(@babel/core@7.26.0) + + balanced-match@1.0.2: {} + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + optional: true + + browserslist@4.24.4: + dependencies: + caniuse-lite: 1.0.30001692 + electron-to-chromium: 1.5.83 + node-releases: 2.0.19 + update-browserslist-db: 1.1.2(browserslist@4.24.4) + + caniuse-lite@1.0.30001692: {} + + change-case@5.4.4: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.1 + optional: true + + colorette@1.4.0: {} + + convert-source-map@2.0.0: {} + + csstype@3.1.3: {} + + debug@4.4.0(supports-color@9.4.0): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 9.4.0 + + defu@6.1.4: {} + + detect-libc@1.0.3: + optional: true + + electron-to-chromium@1.5.83: {} + + entities@4.5.0: {} + + esbuild@0.24.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.2 + '@esbuild/android-arm': 0.24.2 + '@esbuild/android-arm64': 0.24.2 + '@esbuild/android-x64': 0.24.2 + '@esbuild/darwin-arm64': 0.24.2 + '@esbuild/darwin-x64': 0.24.2 + '@esbuild/freebsd-arm64': 0.24.2 + '@esbuild/freebsd-x64': 0.24.2 + '@esbuild/linux-arm': 0.24.2 + '@esbuild/linux-arm64': 0.24.2 + '@esbuild/linux-ia32': 0.24.2 + '@esbuild/linux-loong64': 0.24.2 + '@esbuild/linux-mips64el': 0.24.2 + '@esbuild/linux-ppc64': 0.24.2 + '@esbuild/linux-riscv64': 0.24.2 + '@esbuild/linux-s390x': 0.24.2 + '@esbuild/linux-x64': 0.24.2 + '@esbuild/netbsd-arm64': 0.24.2 + '@esbuild/netbsd-x64': 0.24.2 + '@esbuild/openbsd-arm64': 0.24.2 + '@esbuild/openbsd-x64': 0.24.2 + '@esbuild/sunos-x64': 0.24.2 + '@esbuild/win32-arm64': 0.24.2 + '@esbuild/win32-ia32': 0.24.2 + '@esbuild/win32-x64': 0.24.2 + + escalade@3.2.0: {} + + fast-deep-equal@3.1.3: {} + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + optional: true + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + globals@11.12.0: {} + + html-entities@2.3.3: {} + + https-proxy-agent@7.0.6(supports-color@9.4.0): + dependencies: + agent-base: 7.1.3 + debug: 4.4.0(supports-color@9.4.0) + transitivePeerDependencies: + - supports-color + + immutable@5.0.3: + optional: true + + index-to-position@0.1.2: {} + + is-extglob@2.1.1: + optional: true + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + optional: true + + is-number@7.0.0: + optional: true + + is-what@4.1.16: {} + + js-levenshtein@1.1.6: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-schema-traverse@1.0.0: {} + + json5@2.2.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + merge-anything@5.1.7: + dependencies: + is-what: 4.1.16 + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + optional: true + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.1 + + moment@2.30.1: {} + + ms@2.1.3: {} + + nanoid@3.3.8: {} + + node-addon-api@7.1.1: + optional: true + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-releases@2.0.19: {} + + openapi-fetch@0.13.4: + dependencies: + openapi-typescript-helpers: 0.0.15 + + openapi-typescript-helpers@0.0.15: {} + + openapi-typescript@7.5.2(typescript@5.7.3): + dependencies: + '@redocly/openapi-core': 1.27.2(supports-color@9.4.0) + ansi-colors: 4.1.3 + change-case: 5.4.4 + parse-json: 8.1.0 + supports-color: 9.4.0 + typescript: 5.7.3 + yargs-parser: 21.1.1 + transitivePeerDependencies: + - encoding + + parse-json@8.1.0: + dependencies: + '@babel/code-frame': 7.26.2 + index-to-position: 0.1.2 + type-fest: 4.32.0 + + parse5@7.2.1: + dependencies: + entities: 4.5.0 + + picocolors@1.1.1: {} + + picomatch@2.3.1: + optional: true + + pluralize@8.0.0: {} + + postcss@8.5.1: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + readdirp@4.1.1: + optional: true + + require-from-string@2.0.2: {} + + rollup@4.30.1: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.30.1 + '@rollup/rollup-android-arm64': 4.30.1 + '@rollup/rollup-darwin-arm64': 4.30.1 + '@rollup/rollup-darwin-x64': 4.30.1 + '@rollup/rollup-freebsd-arm64': 4.30.1 + '@rollup/rollup-freebsd-x64': 4.30.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.30.1 + '@rollup/rollup-linux-arm-musleabihf': 4.30.1 + '@rollup/rollup-linux-arm64-gnu': 4.30.1 + '@rollup/rollup-linux-arm64-musl': 4.30.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.30.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.30.1 + '@rollup/rollup-linux-riscv64-gnu': 4.30.1 + '@rollup/rollup-linux-s390x-gnu': 4.30.1 + '@rollup/rollup-linux-x64-gnu': 4.30.1 + '@rollup/rollup-linux-x64-musl': 4.30.1 + '@rollup/rollup-win32-arm64-msvc': 4.30.1 + '@rollup/rollup-win32-ia32-msvc': 4.30.1 + '@rollup/rollup-win32-x64-msvc': 4.30.1 + fsevents: 2.3.3 + + sass@1.83.4: + dependencies: + chokidar: 4.0.3 + immutable: 5.0.3 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.0 + optional: true + + semver@6.3.1: {} + + seroval-plugins@1.2.0(seroval@1.2.0): + dependencies: + seroval: 1.2.0 + + seroval@1.2.0: {} + + solid-apexcharts@0.4.0(apexcharts@4.3.0)(solid-js@1.9.4): + dependencies: + apexcharts: 4.3.0 + defu: 6.1.4 + solid-js: 1.9.4 + + solid-devtools@0.33.0(solid-js@1.9.4)(vite@6.0.7(sass@1.83.4)): + dependencies: + '@babel/core': 7.26.0 + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) + '@babel/types': 7.26.5 + '@solid-devtools/debugger': 0.26.0(solid-js@1.9.4) + '@solid-devtools/shared': 0.19.0(solid-js@1.9.4) + solid-js: 1.9.4 + optionalDependencies: + vite: 6.0.7(sass@1.83.4) + transitivePeerDependencies: + - supports-color + + solid-js@1.9.4: + dependencies: + csstype: 3.1.3 + seroval: 1.2.0 + seroval-plugins: 1.2.0(seroval@1.2.0) + + solid-refresh@0.6.3(solid-js@1.9.4): + dependencies: + '@babel/generator': 7.26.5 + '@babel/helper-module-imports': 7.25.9 + '@babel/types': 7.26.5 + solid-js: 1.9.4 + transitivePeerDependencies: + - supports-color + + source-map-js@1.2.1: {} + + supports-color@9.4.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + optional: true + + tr46@0.0.3: {} + + type-fest@4.32.0: {} + + typescript@5.7.3: {} + + update-browserslist-db@1.1.2(browserslist@4.24.4): + dependencies: + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js-replace@1.0.1: {} + + validate-html-nesting@1.2.2: {} + + vite-plugin-solid@2.11.0(solid-js@1.9.4)(vite@6.0.7(sass@1.83.4)): + dependencies: + '@babel/core': 7.26.0 + '@types/babel__core': 7.20.5 + babel-preset-solid: 1.9.3(@babel/core@7.26.0) + merge-anything: 5.1.7 + solid-js: 1.9.4 + solid-refresh: 0.6.3(solid-js@1.9.4) + vite: 6.0.7(sass@1.83.4) + vitefu: 1.0.5(vite@6.0.7(sass@1.83.4)) + transitivePeerDependencies: + - supports-color + + vite@6.0.7(sass@1.83.4): + dependencies: + esbuild: 0.24.2 + postcss: 8.5.1 + rollup: 4.30.1 + optionalDependencies: + fsevents: 2.3.3 + sass: 1.83.4 + + vitefu@1.0.5(vite@6.0.7(sass@1.83.4)): + optionalDependencies: + vite: 6.0.7(sass@1.83.4) + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + yallist@3.1.1: {} + + yaml-ast-parser@0.0.43: {} + + yargs-parser@21.1.1: {} 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.module.css b/server/frontend/src/App.module.css new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/server/frontend/src/App.module.css diff --git a/server/frontend/src/App.tsx b/server/frontend/src/App.tsx new file mode 100644 index 0000000..bdc1007 --- /dev/null +++ b/server/frontend/src/App.tsx @@ -0,0 +1,22 @@ +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 ( + <> + <Suspense fallback="Loading analysis..."> + <ul> + <For each={analysis()?.data}> + {item => + <li><A href={`/analysis/${item.id}`}>{item.name}</A></li> + } + </For> + </ul> + </Suspense> + </> + ); +}; + +export default App; diff --git a/server/frontend/src/Test.tsx b/server/frontend/src/Test.tsx new file mode 100644 index 0000000..15d2f73 --- /dev/null +++ b/server/frontend/src/Test.tsx @@ -0,0 +1,31 @@ +import { A, createAsync } from "@solidjs/router"; +import { client } from "./api.js"; +import { For, Suspense } from "solid-js"; + +export default function Test() { + let items = createAsync(() => + client.GET("/item", { + params: { + query: { + itemId: ["HYPERION", "BAT_WAND"], + }, + }, + }) + ); + return ( + <> + Test page <A href={"/"}>Back to main</A> + <hr /> + <Suspense fallback={"Loading items..."}> + <p>Here are all Items:</p> + <For each={Object.entries(items()?.data || {})}> + {([id, name]) => ( + <li> + <code>{id}</code>: {name} + </li> + )} + </For> + </Suspense> + </> + ); +} diff --git a/server/frontend/src/api-schema.d.ts b/server/frontend/src/api-schema.d.ts new file mode 100644 index 0000000..7ba1db4 --- /dev/null +++ b/server/frontend/src/api-schema.d.ts @@ -0,0 +1,236 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/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; + }; + "/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; + }; +} +export type webhooks = Record<string, never>; +export interface components { + 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; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + playerId: string; + profileId: string; + }[]; + }; + }; + }; + }; + 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; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + visualizations: { + label: string; + xLabel: string; + yLabel: string; + dataPoints: { + time: number; + value: number; + }[]; + }[]; + }; + }; + }; + }; + }; + 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; + }; + }; + }; + }; + }; + getLogEntries: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + 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 new file mode 100644 index 0000000..8ab6272 --- /dev/null +++ b/server/frontend/src/api.ts @@ -0,0 +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.css b/server/frontend/src/index.css new file mode 100644 index 0000000..4a1df4d --- /dev/null +++ b/server/frontend/src/index.css @@ -0,0 +1,13 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} diff --git a/server/frontend/src/index.tsx b/server/frontend/src/index.tsx new file mode 100644 index 0000000..610a78b --- /dev/null +++ b/server/frontend/src/index.tsx @@ -0,0 +1,23 @@ +/* @refresh reload */ +import { render } from "solid-js/web"; +import 'solid-devtools'; + +import "./index.css"; +import type { RouteDefinition } from "@solidjs/router"; +import { Router } from "@solidjs/router"; +import { lazy } from "solid-js"; + +const root = document.getElementById("root"); + +if (!(root instanceof HTMLElement)) { + throw new Error( + "Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?" + ); +} +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!); diff --git a/server/frontend/tsconfig.json b/server/frontend/tsconfig.json new file mode 100644 index 0000000..548d331 --- /dev/null +++ b/server/frontend/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "allowImportingTsExtensions": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "isolatedModules": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "module": "Preserve", + "moduleResolution": "bundler", + "noEmit": true, + "noUncheckedIndexedAccess": true, + "strict": true, + "target": "ESNext", + "types": [ + "vite/client" + ] + } +} diff --git a/server/frontend/vite.config.ts b/server/frontend/vite.config.ts new file mode 100644 index 0000000..4d3fdd1 --- /dev/null +++ b/server/frontend/vite.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite'; +import solidPlugin from 'vite-plugin-solid'; +import devtools from 'solid-devtools/vite'; +export default defineConfig({ + plugins: [ + solidPlugin(), + devtools({ + autoname: true + }) + ], + server: { + port: 3000, + }, + build: { + target: 'esnext', + }, +}); diff --git a/server/swagger/build.gradle.kts b/server/swagger/build.gradle.kts new file mode 100644 index 0000000..76e5f78 --- /dev/null +++ b/server/swagger/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + `java-library` + kotlin("jvm") + kotlin("plugin.serialization") +} + + +dependencies { + declareKtorVersion() + api("io.ktor:ktor-server-core") + api("sh.ondr:kotlin-json-schema:0.1.1") + implementation("org.webjars:swagger-ui:5.18.2") +} + +java { + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) +} diff --git a/server/swagger/src/main/kotlin/moe/nea/ledger/server/core/api/OpenApiModel.kt b/server/swagger/src/main/kotlin/moe/nea/ledger/server/core/api/OpenApiModel.kt new file mode 100644 index 0000000..af9d3f4 --- /dev/null +++ b/server/swagger/src/main/kotlin/moe/nea/ledger/server/core/api/OpenApiModel.kt @@ -0,0 +1,117 @@ +package moe.nea.ledger.server.core.api + +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import sh.ondr.jsonschema.JsonSchema + +@Serializable +data class OpenApiModel( + val openapi: String = "3.0.0", + val info: Info, + val servers: List<Server>, + val paths: Map<OpenApiPath, OpenApiRoute>, +) + +@Serializable // TODO: custom serializer +@JvmInline +value class OpenApiPath(val name: String) + +@Serializable +data class OpenApiRoute( + val summary: String, + val description: String, + val get: OpenApiOperation?, + val put: OpenApiOperation?, + val patch: OpenApiOperation?, + val post: OpenApiOperation?, + val delete: OpenApiOperation?, +) + +@Serializable +data class OpenApiOperation( + val tags: List<Tag>, + val summary: String, + val description: String, + val operationId: String, + val deprecated: Boolean, + val parameters: List<OpenApiParameter>, + val responses: Map<@Serializable(HttpStatusCodeIntAsString::class) HttpStatusCode, OpenApiResponse> +) + +@Serializable +data class OpenApiParameter( + @SerialName("in") val location: ParameterLocation, + val name: String, + val description: String, + val schema: JsonSchema?, + val required: Boolean = true, +) + +@Serializable +enum class ParameterLocation { + @SerialName("query") + QUERY, + @SerialName("path") + PATH, +} + +object HttpStatusCodeIntAsString : KSerializer<HttpStatusCode> { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("HttpStatusCodeIntAsString", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): HttpStatusCode { + return HttpStatusCode.fromValue(decoder.decodeString().toInt()) + } + + override fun serialize(encoder: Encoder, value: HttpStatusCode) { + encoder.encodeString(value.value.toString()) + } +} + +object ContentTypeSerializer : KSerializer<ContentType> { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ContentTypeSerializer", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): ContentType { + return ContentType.parse(decoder.decodeString()) + } + + override fun serialize(encoder: Encoder, value: ContentType) { + encoder.encodeString(value.contentType + "/" + value.contentSubtype) + } +} + +@Serializable +data class OpenApiResponse( + val description: String, + val content: Map<@Serializable(ContentTypeSerializer::class) ContentType, OpenApiResponseContentType> +) + +@Serializable +data class OpenApiResponseContentType( + val schema: JsonSchema? +) + +@Serializable +@JvmInline +value class Tag(val name: String) + +@Serializable +data class Info( + val title: String, + val description: String, + val version: String, +) + +@Serializable +data class Server( + val url: String, + val description: String, +) diff --git a/server/swagger/src/main/kotlin/moe/nea/ledger/server/core/api/docs.kt b/server/swagger/src/main/kotlin/moe/nea/ledger/server/core/api/docs.kt new file mode 100644 index 0000000..c1b550d --- /dev/null +++ b/server/swagger/src/main/kotlin/moe/nea/ledger/server/core/api/docs.kt @@ -0,0 +1,323 @@ +package moe.nea.ledger.server.core.api + +import io.ktor.http.ContentType +import io.ktor.http.HttpMethod +import io.ktor.http.HttpStatusCode +import io.ktor.http.content.OutgoingContent +import io.ktor.http.defaultForFilePath +import io.ktor.server.application.ApplicationCallPipeline +import io.ktor.server.application.BaseApplicationPlugin +import io.ktor.server.application.host +import io.ktor.server.application.port +import io.ktor.server.response.respond +import io.ktor.server.response.respondText +import io.ktor.server.routing.HttpMethodRouteSelector +import io.ktor.server.routing.PathSegmentConstantRouteSelector +import io.ktor.server.routing.RootRouteSelector +import io.ktor.server.routing.Route +import io.ktor.server.routing.RoutingNode +import io.ktor.server.routing.TrailingSlashRouteSelector +import io.ktor.server.routing.get +import io.ktor.server.routing.route +import io.ktor.util.AttributeKey +import io.ktor.util.cio.KtorDefaultPool +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.jvm.javaio.toByteReadChannel +import kotlinx.serialization.json.JsonPrimitive +import sh.ondr.jsonschema.JsonSchema +import sh.ondr.jsonschema.jsonSchema +import java.io.File +import java.io.InputStream + + +fun Route.openApiDocsJson() { + get { + val docs = plugin(Documentation) + val model = docs.finalizeJson() + call.respond(model) + } +} + +fun Route.openApiUi(apiJsonUrl: String) { + get("swagger-initializer.js") { + call.respondText( + //language=JavaScript + """ + window.onload = function() { + //<editor-fold desc="Changeable Configuration Block"> + + // the following lines will be replaced by docker/configurator, when it runs in a docker-container + window.ui = SwaggerUIBundle({ + url: ${JsonPrimitive(apiJsonUrl)}, + dom_id: '#swagger-ui', + deepLinking: true, + presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIStandalonePreset + ], + plugins: [ + SwaggerUIBundle.plugins.DownloadUrl + ], + layout: "StandaloneLayout" + }); + + //</editor-fold> + }; + """.trimIndent()) + } +// val swaggerUiProperties = +// environment.classLoader.getResource("/META-INF/maven/org.webjars/swagger-ui/pom.properties") +// ?: error("Could not find swagger webjar") +// val swaggerUiZip = swaggerUiProperties.toString().substringBefore("!") + val pathParameterName = "static-content-path-parameter" + route("{$pathParameterName...}") { + get { + var requestedPath = call.parameters.getAll(pathParameterName)?.joinToString(File.separator) ?: "" + requestedPath = requestedPath.replace("\\", "/") + if (requestedPath.isEmpty()) requestedPath = "index.html" + if (requestedPath.contains("..")) { + call.respondText("Forbidden", status = HttpStatusCode.Forbidden) + return@get + } + //TODO: I mean i should read out the version properties but idc + val version = "5.18.2" + val resource = + environment.classLoader.getResourceAsStream("META-INF/resources/webjars/swagger-ui/$version/$requestedPath") + + if (resource == null) { + call.respondText("Not Found", status = HttpStatusCode.NotFound) + return@get + } + + call.respond(InputStreamContent(resource, ContentType.defaultForFilePath(requestedPath))) + } + } +} + +internal class InputStreamContent( + private val input: InputStream, + override val contentType: ContentType +) : OutgoingContent.ReadChannelContent() { + + override fun readFrom(): ByteReadChannel = input.toByteReadChannel(pool = KtorDefaultPool) +} + +class DocumentationPath(val path: String) + +class DocumentationEndpoint private constructor() { + var method: HttpMethod = HttpMethod.Get + private set + lateinit var path: DocumentationPath + private set + + private fun initFromPath( + baseRoute: Route?, + route: RoutingNode, + ) { + path = DocumentationPath(createRoutePath(baseRoute, route)) + } + + private fun createRoutePath( + baseRoute: Route?, + route: RoutingNode, + ): String { + if (baseRoute == route) + return "/" + val parent = route.parent + if (parent == null) { + if (baseRoute != null) + error("Could not find $route in $baseRoute") + return "/" + } + val parentPath = createRoutePath(baseRoute, parent) + var parentPathAppendable = parentPath + if (!parentPathAppendable.endsWith("/")) + parentPathAppendable += "/" + return when (val selector = route.selector) { + is TrailingSlashRouteSelector -> parentPathAppendable + is RootRouteSelector -> parentPath + is PathSegmentConstantRouteSelector -> parentPathAppendable + selector.value + is HttpMethodRouteSelector -> { + method = selector.method + parentPath + } + + else -> error("Could not comprehend $selector (${selector.javaClass})") + } + } + + companion object { + fun createDocumentationPath(baseRoute: Route?, route: Route): DocumentationEndpoint { + val path = DocumentationEndpoint() + path.initFromPath(baseRoute, route as RoutingNode) + return path + } + } +} + +class Response { + var schema: JsonSchema? = null + inline fun <reified T : Any> schema() { + schema = jsonSchema<T>() + } + + fun intoJson(): OpenApiResponse { + return OpenApiResponse( + "", + mapOf( + ContentType.Application.Json to OpenApiResponseContentType(schema) + ) + ) + } +} + +interface IntoTag { + fun intoTag(): String +} + +class DocumentationOperationContext(val route: DocumentationContext) { + val responses = mutableMapOf<HttpStatusCode, Response>() + fun responds(statusCode: HttpStatusCode, block: Response.() -> Unit) { + responses.getOrPut(statusCode) { Response() }.also(block) + } + + fun respondsOk(block: Response.() -> Unit) { + responds(HttpStatusCode.OK, block) + } + + var summary: String = "" + var description: String = "" + var deprecated: Boolean = false + var operationId: String = "" + val tags: MutableList<String> = mutableListOf() + val parameters: MutableList<OpenApiParameter> = mutableListOf() + fun tag(vararg tag: String) { + tags.addAll(tag) + } + + fun tag(vararg tag: IntoTag) { + tag.mapTo(tags) { it.intoTag() } + } + + inline fun <reified T : Any> queryParameter(name: String, description: String = "") { + parameter(ParameterLocation.QUERY, name, description, jsonSchema<T>()) + } + + fun parameter( + location: ParameterLocation, name: String, + description: String = "", schema: JsonSchema? = null + ) { + parameters.add(OpenApiParameter( + location, name, description, + schema + )) + } + + fun intoJson(): OpenApiOperation { + return OpenApiOperation( + tags = tags.map { Tag(it) }, + summary = summary, + description = description, + operationId = operationId, + deprecated = deprecated, + parameters = parameters, + responses = responses.mapValues { + it.value.intoJson() + } + ) + + } +} + +class DocumentationContext(val path: DocumentationPath) { + val ops: MutableMap<HttpMethod, DocumentationOperationContext> = mutableMapOf() + var summary: String = "" + var description = "" + fun intoJson(): OpenApiRoute { + return OpenApiRoute( + summary, + description, + get = ops[HttpMethod.Get]?.intoJson(), + put = ops[HttpMethod.Put]?.intoJson(), + post = ops[HttpMethod.Post]?.intoJson(), + patch = ops[HttpMethod.Patch]?.intoJson(), + delete = ops[HttpMethod.Delete]?.intoJson(), + ) + } + + fun createOperationNode(method: HttpMethod): DocumentationOperationContext { + return ops.getOrPut(method) { DocumentationOperationContext(this) } + } +} + + +class Documentation(config: Configuration) { + companion object Plugin : BaseApplicationPlugin<ApplicationCallPipeline, Configuration, Documentation> { + override val key: AttributeKey<Documentation> = AttributeKey("LedgerDocumentation") + + override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): Documentation { + val config = Configuration().also(configure) + if (config.servers.isEmpty()) { + config.servers.add(Server( + "http://${pipeline.environment.config.host}:${pipeline.environment.config.port}", + "Server", + )) + } + val plugin = Documentation(config) + return plugin + } + } + + val info = config.info + var root: RoutingNode? = null + private set + val servers: List<Server> = config.servers + + private val documentationNodes = mutableMapOf<DocumentationPath, DocumentationContext>() + fun createDocumentationNode(endpoint: DocumentationEndpoint) = + documentationNodes.getOrPut(endpoint.path) { DocumentationContext(endpoint.path) } + .createOperationNode(endpoint.method) + + private val openApiJson by lazy { + OpenApiModel( + info = info, + servers = servers, + paths = documentationNodes.map { + OpenApiPath(it.key.path) to it.value.intoJson() + }.toMap() + ) + } + + fun finalizeJson(): OpenApiModel { + return openApiJson + } + + fun setRootNode(routingNode: RoutingNode) { + require(documentationNodes.isEmpty()) { "Cannot set API root node after routes have been documented: ${documentationNodes.keys}" } + this.root = routingNode + } + + class Configuration { + var info: Info = Info( + title = "Example API Docs", + description = "Missing description", + version = "0.0.0" + ) + val servers: MutableList<Server> = mutableListOf() + } +} + +fun Route.docs(block: DocumentationOperationContext.() -> Unit) { + val documentation = plugin(Documentation) + val documentationPath = DocumentationEndpoint.createDocumentationPath(documentation.root, this) + val node = documentation.createDocumentationNode(documentationPath) + block(node) +} + +/** + * Mark this current routing node as API route. Note that this will not apply retroactively and all api requests must be declared relative to this one. + */ +fun Route.setApiRoot() { + plugin(Documentation).setRootNode(this as RoutingNode) +} + |