diff --git a/.gitignore b/.gitignore index 87907f5..ffce916 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ *.out coverage.* +# Admin SPA +/admin/node_modules/ + # Env and local config .env .env.* diff --git a/admin/env.d.ts b/admin/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/admin/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/admin/index.html b/admin/index.html new file mode 100644 index 0000000..30bc6ca --- /dev/null +++ b/admin/index.html @@ -0,0 +1,14 @@ + + + + + + + + SummerCMS + + +
+ + + diff --git a/admin/openapi/admin.json b/admin/openapi/admin.json new file mode 100644 index 0000000..b8a6386 --- /dev/null +++ b/admin/openapi/admin.json @@ -0,0 +1,2238 @@ +{ + "components": { + "schemas": { + "cabana.AdminLoginData": { + "properties": { + "access_token": { + "type": "string" + }, + "expires_in": { + "type": "integer" + }, + "token_type": { + "type": "string" + } + }, + "required": [ + "token_type" + ], + "type": "object" + }, + "cabana.AdminLoginRequest": { + "properties": { + "email": { + "type": "string" + }, + "login": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "password" + ], + "type": "object" + }, + "cabana.AdminProfile": { + "properties": { + "email": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_superuser": { + "type": "boolean" + }, + "last_name": { + "type": "string" + }, + "login": { + "type": "string" + }, + "role": { + "$ref": "#/components/schemas/cabana.AdminRoleSummary" + } + }, + "required": [ + "email", + "first_name", + "id", + "is_superuser", + "last_name", + "login" + ], + "type": "object" + }, + "cabana.AdminRoleSummary": { + "properties": { + "code": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "required": [ + "code", + "id", + "name" + ], + "type": "object" + }, + "cabana.BulkAction": { + "properties": { + "label": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "cabana.Envelope-array_cabana_NavigationEntry": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/cabana.NavigationEntry" + }, + "type": "array" + }, + "meta": { + "$ref": "#/components/schemas/cabana.SuccessMeta" + } + }, + "required": [ + "data", + "meta" + ], + "type": "object" + }, + "cabana.Envelope-cabana_AdminLoginData": { + "properties": { + "data": { + "$ref": "#/components/schemas/cabana.AdminLoginData" + }, + "meta": { + "$ref": "#/components/schemas/cabana.SuccessMeta" + } + }, + "required": [ + "data", + "meta" + ], + "type": "object" + }, + "cabana.Envelope-cabana_AdminProfile": { + "properties": { + "data": { + "$ref": "#/components/schemas/cabana.AdminProfile" + }, + "meta": { + "$ref": "#/components/schemas/cabana.SuccessMeta" + } + }, + "required": [ + "data", + "meta" + ], + "type": "object" + }, + "cabana.Envelope-cabana_ListSchema": { + "properties": { + "data": { + "$ref": "#/components/schemas/cabana.ListSchema" + }, + "meta": { + "$ref": "#/components/schemas/cabana.SuccessMeta" + } + }, + "required": [ + "data", + "meta" + ], + "type": "object" + }, + "cabana.ErrorBody": { + "properties": { + "code": { + "type": "string" + }, + "details": { + "additionalProperties": {}, + "type": "object" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "details", + "message" + ], + "type": "object" + }, + "cabana.ErrorEnvelope": { + "properties": { + "error": { + "$ref": "#/components/schemas/cabana.ErrorBody" + } + }, + "required": [ + "error" + ], + "type": "object" + }, + "cabana.FormMeta": { + "properties": { + "locale": { + "type": "string" + } + }, + "required": [ + "locale" + ], + "type": "object" + }, + "cabana.FormOption": { + "properties": { + "label": { + "type": "string" + }, + "value": { + "$ref": "#/components/schemas/cabana.jsonScalar" + } + }, + "required": [ + "label", + "value" + ], + "type": "object" + }, + "cabana.ListColumn": { + "properties": { + "key": { + "type": "string" + }, + "label": { + "type": "string" + }, + "relation": { + "type": "string" + }, + "searchable": { + "type": "boolean" + }, + "select": { + "type": "string" + }, + "sortable": { + "type": "boolean" + }, + "type": { + "type": "string" + } + }, + "required": [ + "key", + "label", + "searchable", + "sortable" + ], + "type": "object" + }, + "cabana.ListEnvelope-array_cabana_AdminRecord": { + "properties": { + "data": { + "items": { + "additionalProperties": {}, + "type": "object" + }, + "type": "array" + }, + "meta": { + "$ref": "#/components/schemas/cabana.ListMeta" + } + }, + "required": [ + "data", + "meta" + ], + "type": "object" + }, + "cabana.ListFilter": { + "properties": { + "column": { + "type": "string" + }, + "falseValue": { + "$ref": "#/components/schemas/cabana.jsonScalar" + }, + "label": { + "type": "string" + }, + "modelClass": { + "type": "string" + }, + "name": { + "type": "string" + }, + "nameFrom": { + "type": "string" + }, + "options": { + "items": { + "$ref": "#/components/schemas/cabana.FormOption" + }, + "type": "array" + }, + "scope": { + "type": "string" + }, + "trueValue": { + "$ref": "#/components/schemas/cabana.jsonScalar" + }, + "type": { + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object" + }, + "cabana.ListMeta": { + "properties": { + "last_page": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "per_page": { + "type": "integer" + }, + "total": { + "type": "integer" + } + }, + "required": [ + "last_page", + "page", + "per_page", + "total" + ], + "type": "object" + }, + "cabana.ListSchema": { + "properties": { + "bulkActions": { + "items": { + "$ref": "#/components/schemas/cabana.BulkAction" + }, + "type": "array" + }, + "columns": { + "items": { + "$ref": "#/components/schemas/cabana.ListColumn" + }, + "type": "array" + }, + "defaultSort": { + "$ref": "#/components/schemas/cabana.ListSort" + }, + "filters": { + "items": { + "$ref": "#/components/schemas/cabana.ListFilter" + }, + "type": "array" + }, + "meta": { + "$ref": "#/components/schemas/cabana.FormMeta" + }, + "modelClass": { + "type": "string" + }, + "noRecordsMessage": { + "type": "string" + }, + "perPageOptions": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "recordUrl": { + "type": "string" + }, + "recordsPerPage": { + "type": "integer" + }, + "rowActions": { + "items": { + "$ref": "#/components/schemas/cabana.RowAction" + }, + "type": "array" + }, + "searchPrompt": { + "type": "string" + }, + "searchTerm": { + "type": "string" + }, + "showCheckboxes": { + "type": "boolean" + }, + "showSearch": { + "type": "boolean" + }, + "showSetup": { + "type": "boolean" + }, + "showSorting": { + "type": "boolean" + }, + "title": { + "type": "string" + }, + "toolbarButtons": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "bulkActions", + "columns", + "filters", + "perPageOptions", + "recordsPerPage", + "rowActions", + "searchTerm", + "showCheckboxes", + "showSearch", + "showSetup", + "showSorting", + "toolbarButtons" + ], + "type": "object" + }, + "cabana.ListSort": { + "properties": { + "column": { + "type": "string" + }, + "direction": { + "type": "string" + } + }, + "required": [ + "column", + "direction" + ], + "type": "object" + }, + "cabana.NavigationEntry": { + "properties": { + "code": { + "type": "string" + }, + "controller": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "label": { + "type": "string" + }, + "order": { + "type": "integer" + }, + "sideMenu": { + "items": { + "$ref": "#/components/schemas/cabana.NavigationEntry" + }, + "type": "array" + } + }, + "required": [ + "code", + "controller", + "icon", + "label", + "order", + "sideMenu" + ], + "type": "object" + }, + "cabana.RowAction": { + "properties": { + "label": { + "type": "string" + }, + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "cabana.SuccessEnvelope": { + "properties": { + "data": {}, + "meta": { + "$ref": "#/components/schemas/cabana.SuccessMeta" + } + }, + "required": [ + "data", + "meta" + ], + "type": "object" + }, + "cabana.SuccessMeta": { + "properties": { + "last_page": { + "type": "integer" + }, + "locale": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "per_page": { + "type": "integer" + }, + "total": { + "type": "integer" + } + }, + "type": "object" + }, + "cabana.jsonScalar": { + "nullable": true, + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ] + } + }, + "securitySchemes": { + "BackendBearer": { + "description": "Backend admin bearer token. Send \"Bearer {access_token}\".", + "in": "header", + "name": "Authorization", + "type": "apiKey" + } + } + }, + "info": { + "contact": {}, + "description": "Framework admin API consumed by the embedded admin SPA. Every path is relative to {backend.uri}/api/v1 (for example /backend/api/v1). The SPA authenticates with the HttpOnly summer_admin cookie set by a login that sends X-Requested-With: XMLHttpRequest, and sends that header on every request; CLI clients and tests send the BackendBearer Authorization header instead.", + "title": "SummerCMS Admin API", + "version": "1" + }, + "openapi": "3.0.3", + "paths": { + "/auth/login": { + "post": { + "parameters": [ + { + "description": "XMLHttpRequest selects cookie transport", + "in": "header", + "name": "X-Requested-With", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.AdminLoginRequest" + } + } + }, + "description": "Credentials", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.Envelope-cabana_AdminLoginData" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + } + }, + "summary": "Admin login", + "tags": [ + "admin" + ] + } + }, + "/auth/logout": { + "post": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.SuccessEnvelope" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "Admin logout", + "tags": [ + "admin" + ] + } + }, + "/auth/me": { + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.Envelope-cabana_AdminProfile" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "Current admin", + "tags": [ + "admin" + ] + } + }, + "/auth/refresh": { + "post": { + "parameters": [ + { + "description": "XMLHttpRequest; required unless a Bearer token is sent", + "in": "header", + "name": "X-Requested-With", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.Envelope-cabana_AdminLoginData" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "summary": "Refresh an admin token", + "tags": [ + "admin" + ] + } + }, + "/navigation": { + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.Envelope-array_cabana_NavigationEntry" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "Admin navigation", + "tags": [ + "admin" + ] + } + }, + "/settings": { + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.SuccessEnvelope" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "List admin settings", + "tags": [ + "admin" + ] + } + }, + "/settings/{code}": { + "get": { + "parameters": [ + { + "description": "Settings code", + "in": "path", + "name": "code", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.SuccessEnvelope" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "Read admin settings", + "tags": [ + "admin" + ] + }, + "put": { + "parameters": [ + { + "description": "Settings code", + "in": "path", + "name": "code", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.SuccessEnvelope" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Forbidden" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "Update admin settings", + "tags": [ + "admin" + ] + } + }, + "/settings/{code}/schema": { + "get": { + "parameters": [ + { + "description": "Settings code", + "in": "path", + "name": "code", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.SuccessEnvelope" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "Admin settings schema", + "tags": [ + "admin" + ] + } + }, + "/{vendor}/{plugin}/{controller}": { + "get": { + "parameters": [ + { + "description": "Vendor", + "in": "path", + "name": "vendor", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Plugin", + "in": "path", + "name": "plugin", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Controller", + "in": "path", + "name": "controller", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Search term", + "in": "query", + "name": "search", + "schema": { + "type": "string" + } + }, + { + "description": "Sort column", + "in": "query", + "name": "sort", + "schema": { + "type": "string" + } + }, + { + "description": "Sort direction (asc or desc)", + "in": "query", + "name": "dir", + "schema": { + "type": "string" + } + }, + { + "description": "Page", + "in": "query", + "name": "page", + "schema": { + "type": "integer" + } + }, + { + "description": "Records per page", + "in": "query", + "name": "per_page", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ListEnvelope-array_cabana_AdminRecord" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Forbidden" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "List admin records", + "tags": [ + "admin" + ] + }, + "post": { + "parameters": [ + { + "description": "Vendor", + "in": "path", + "name": "vendor", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Plugin", + "in": "path", + "name": "plugin", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Controller", + "in": "path", + "name": "controller", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.SuccessEnvelope" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Forbidden" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "Create an admin record", + "tags": [ + "admin" + ] + } + }, + "/{vendor}/{plugin}/{controller}/bulk-delete": { + "post": { + "parameters": [ + { + "description": "Vendor", + "in": "path", + "name": "vendor", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Plugin", + "in": "path", + "name": "plugin", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Controller", + "in": "path", + "name": "controller", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.SuccessEnvelope" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Forbidden" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "Bulk-delete admin records", + "tags": [ + "admin" + ] + } + }, + "/{vendor}/{plugin}/{controller}/schema/form": { + "get": { + "parameters": [ + { + "description": "Vendor", + "in": "path", + "name": "vendor", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Plugin", + "in": "path", + "name": "plugin", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Controller", + "in": "path", + "name": "controller", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.SuccessEnvelope" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "Admin form schema", + "tags": [ + "admin" + ] + } + }, + "/{vendor}/{plugin}/{controller}/schema/list": { + "get": { + "parameters": [ + { + "description": "Vendor", + "in": "path", + "name": "vendor", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Plugin", + "in": "path", + "name": "plugin", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Controller", + "in": "path", + "name": "controller", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.Envelope-cabana_ListSchema" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "Admin list schema", + "tags": [ + "admin" + ] + } + }, + "/{vendor}/{plugin}/{controller}/schema/relation/{name}": { + "get": { + "parameters": [ + { + "description": "Vendor", + "in": "path", + "name": "vendor", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Plugin", + "in": "path", + "name": "plugin", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Controller", + "in": "path", + "name": "controller", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Relation name", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.SuccessEnvelope" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "Admin relation schema", + "tags": [ + "admin" + ] + } + }, + "/{vendor}/{plugin}/{controller}/{id}": { + "delete": { + "parameters": [ + { + "description": "Vendor", + "in": "path", + "name": "vendor", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Plugin", + "in": "path", + "name": "plugin", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Controller", + "in": "path", + "name": "controller", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Record id", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.SuccessEnvelope" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "Delete an admin record", + "tags": [ + "admin" + ] + }, + "get": { + "parameters": [ + { + "description": "Vendor", + "in": "path", + "name": "vendor", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Plugin", + "in": "path", + "name": "plugin", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Controller", + "in": "path", + "name": "controller", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Record id", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.SuccessEnvelope" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "Show an admin record", + "tags": [ + "admin" + ] + }, + "put": { + "parameters": [ + { + "description": "Vendor", + "in": "path", + "name": "vendor", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Plugin", + "in": "path", + "name": "plugin", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Controller", + "in": "path", + "name": "controller", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Record id", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.SuccessEnvelope" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Forbidden" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "Update an admin record", + "tags": [ + "admin" + ] + } + }, + "/{vendor}/{plugin}/{controller}/{id}/relations/{name}": { + "get": { + "parameters": [ + { + "description": "Vendor", + "in": "path", + "name": "vendor", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Plugin", + "in": "path", + "name": "plugin", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Controller", + "in": "path", + "name": "controller", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Owner id", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "description": "Relation name", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.SuccessEnvelope" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "List linked relation records", + "tags": [ + "admin" + ] + } + }, + "/{vendor}/{plugin}/{controller}/{id}/relations/{name}/candidates": { + "get": { + "parameters": [ + { + "description": "Vendor", + "in": "path", + "name": "vendor", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Plugin", + "in": "path", + "name": "plugin", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Controller", + "in": "path", + "name": "controller", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Owner id", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "description": "Relation name", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.SuccessEnvelope" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "List relation candidates", + "tags": [ + "admin" + ] + } + }, + "/{vendor}/{plugin}/{controller}/{id}/relations/{name}/link": { + "post": { + "parameters": [ + { + "description": "Vendor", + "in": "path", + "name": "vendor", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Plugin", + "in": "path", + "name": "plugin", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Controller", + "in": "path", + "name": "controller", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Owner id", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "description": "Relation name", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.SuccessEnvelope" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Forbidden" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "Link relation records", + "tags": [ + "admin" + ] + } + }, + "/{vendor}/{plugin}/{controller}/{id}/relations/{name}/unlink": { + "post": { + "parameters": [ + { + "description": "Vendor", + "in": "path", + "name": "vendor", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Plugin", + "in": "path", + "name": "plugin", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Controller", + "in": "path", + "name": "controller", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Owner id", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "description": "Relation name", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.SuccessEnvelope" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Forbidden" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/cabana.ErrorEnvelope" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "BackendBearer": [] + } + ], + "summary": "Unlink relation records", + "tags": [ + "admin" + ] + } + } + } +} diff --git a/admin/package-lock.json b/admin/package-lock.json new file mode 100644 index 0000000..60c375e --- /dev/null +++ b/admin/package-lock.json @@ -0,0 +1,4403 @@ +{ + "name": "summercms-admin", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "summercms-admin", + "dependencies": { + "@fontsource/dm-mono": "5.3.0", + "@fontsource/dm-sans": "5.3.0", + "@lucide/vue": "1.17.0", + "openapi-fetch": "0.17.0", + "reka-ui": "2.9.10", + "vue": "3.5.35", + "vue-router": "5.1.0" + }, + "devDependencies": { + "@tailwindcss/vite": "4.3.0", + "@vitejs/plugin-vue": "6.0.8", + "@vue/test-utils": "2.4.11", + "happy-dom": "20.11.6", + "openapi-typescript": "7.13.0", + "tailwindcss": "4.3.0", + "typescript": "5.9.3", + "vite": "7.3.5", + "vitest": "3.2.7", + "vue-tsc": "3.3.11" + }, + "engines": { + "node": ">=22.6" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.6.tgz", + "integrity": "sha512-/pcanjdRsonCexCuxgGD8fKnBLLzk2i9uM4kmcmGyyev2R5YDs+rafMYZ89RvaGY1mW8T7C5au0TyYaEeHBGng==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.6", + "@babel/types": "^8.0.6", + "@jridgewell/gen-mapping": "0.4.0-beta.0", + "@jridgewell/trace-mapping": "^0.3.31", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/helper-string-parser": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.6.tgz", + "integrity": "sha512-+6HYEBnsDRZHJtUTAEfMQd0zMDqdFHS7C+5vAsEZqBUr3vEMWOWlHJ0Jz1gS8jJIlCvLsuA6ivmeoqzH936YcA==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.6.tgz", + "integrity": "sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/parser": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.6.tgz", + "integrity": "sha512-LpGDIYJAzc3Y3PT9x+FxBrGYu2PlWxLmyN7SwPTzaX0LSwEmFJBsRnPBqmaXF2r1v+Zhz6lNiEJ+7yKtM5+Ugg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/types": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.6.tgz", + "integrity": "sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.6", + "@babel/helper-validator-identifier": "^8.0.6" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@floating-ui/vue": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.11.tgz", + "integrity": "sha512-HzHKCNVxnGS35r9fCHBc3+uCnjw9IWIlCPL683cGgM9Kgj2BiAl8x1mS7vtvP6F9S/e/q4O6MApwSHj8hNLGfw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6", + "@floating-ui/utils": "^0.2.11", + "vue-demi": ">=0.13.0" + } + }, + "node_modules/@floating-ui/vue/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@fontsource/dm-mono": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/dm-mono/-/dm-mono-5.3.0.tgz", + "integrity": "sha512-OINjI8C1S/wpchhQxl7njZdMn4+hnDCpQ4YtvvOpKNARo+0J8O1x1IcrChxNjHOhfVv1by8C/FQoy3hXK+C1Ug==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/dm-sans": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/dm-sans/-/dm-sans-5.3.0.tgz", + "integrity": "sha512-lYJtMXXO28q1z+yz+z8XKd0s4hXaa9QdkETzkyD760sidCv5heI86weYA0sx0Nc4pAMAQTUuyf4gO44cYKKS9g==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@internationalized/date": { + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.4.tgz", + "integrity": "sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.8", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.8.tgz", + "integrity": "sha512-8UmMFia46DUt+k97zKd9fKWXcWHR+k8ae3eYzILETuT2KbIvLyOfac7zesw+sJdRAAZ7Q9pM1Mk22aXp2LD0Ig==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.4.0-beta.0", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.4.0-beta.0.tgz", + "integrity": "sha512-JdGNkbE4GlNPYQhM0L95fBQr7ctLZJ276QXQLTad4t1oSdnnCI3fDq9DW3BqYAWv8Wc3+HS+4Gsii1oPMCfz1w==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0-beta.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lucide/vue": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@lucide/vue/-/vue-1.17.0.tgz", + "integrity": "sha512-6Q1ZHgr5FbmJzKWe5BxlNdjLj2lbmuH1zwDtVzUJofX0w9UREwKgq4F4jwKqFYyyIS4Rj3FiJvDi2k6djukmmw==", + "license": "ISC", + "peerDependencies": { + "vue": ">=3.0.1" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redocly/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js-replace": "^1.0.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@redocly/config": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.0.tgz", + "integrity": "sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@redocly/openapi-core": { + "version": "1.34.20", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.20.tgz", + "integrity": "sha512-ypeBZ/6BKXR9+7/TtbKhbl4UgD7raHhPS12oknlKno2A8+lnFkxIwiE/Aklu6L2cd/ioH+fCWuMxi9/p3EyAPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/ajv": "8.11.2", + "@redocly/config": "0.22.0", + "colorette": "1.4.0", + "https-proxy-agent": "7.0.6", + "js-levenshtein": "1.1.6", + "js-yaml": "4.3.2", + "minimatch": "5.1.9", + "pluralize": "8.0.0", + "yaml-ast-parser": "0.0.43" + }, + "engines": { + "node": ">=18.17.0", + "npm": ">=9.5.0" + } + }, + "node_modules/@redocly/openapi-core/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.5.tgz", + "integrity": "sha512-J25QJU+B78T4FhhBsNpLJyVWOi31mwtpcMwywHmOKH65Q9IWGA81gPj+dnwlhU8wktVriYE+tFAaQgrnJRzAZg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.5.tgz", + "integrity": "sha512-LDopB3zuZM5Ux9TT2luNEBJW/tYbGU2g1d+VpKk6I+gSKDb+/7sYE6M225gRQt4RbMX6MSwMsVR/phdjVUgRLg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.5.tgz", + "integrity": "sha512-wlJEERGfeuHeBavCL2qVnNacOK43NDoZM4sjkeRPymd04OAE9T1zBqDJgmZ+CIsPTYKwdzpUC8vmOw84dwY4Tg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.5.tgz", + "integrity": "sha512-4nJJGg5jbo2wwPP4JP+LfEBA3bvP8rU9CLuhp7jWvq9sxEyhjQFTFdrqi+/dHEin/pd8jpT0vcehIpnZtmEdcQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.5.tgz", + "integrity": "sha512-DrZbyCDF1hneuO6jRbvZ2D7+PIBM6yIwYnJpg2vIk58T+wuFpiaGZrfUr59lDWw45bg+IrpTGLPiNi/Fk4w3Cg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.5.tgz", + "integrity": "sha512-gqfUVMJMB3mehqywxp6hTBFfgtMQykZY19+cfiaYP0toIJLb/1DZRJHVkQQGP13W4TAwfZDWeg1qBcheTRioXQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.5.tgz", + "integrity": "sha512-CFmhpvAwzSaWMlN3VN7UtmoTihlZNzoP0juQib5TQRnYUyDV8dXeWOp29sobWAT6gXl/hQgAClLlEiYozQG3OQ==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.5.tgz", + "integrity": "sha512-Uc9H8eXCOayV6JLTH5bXKMId6qbhNHa818/BgYjm4jrlq3vZquC9cqyvHBw17xy5Mnj5f+I3gFK5JcEf3hSqrw==", + "cpu": [ + "arm" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.5.tgz", + "integrity": "sha512-VcPr/szv/1BFw112Kt//fxulXt/JPqzzidU84iW68L2DdjnOO8QFUv2zTSYBEPHD6movBD4z+bbr5y60GYM7Jw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.5.tgz", + "integrity": "sha512-BnxtJ5/91BrIHYIkGrmjz/lbMhqEHt1dPFqIxIFR+jPn0xVc/oUSCtIT089zfp5ufwGDlYz2UC+Fe1SRBpYFbQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.5.tgz", + "integrity": "sha512-LrYcHZwF+fAMNKHYTOQ5osWM4AZF7YF6D+XtsjDyEvljtt11twc+zHVXBLNEjxVSUnKYsOhvVz4Z213eW02COQ==", + "cpu": [ + "loong64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.5.tgz", + "integrity": "sha512-nj7QKQePAAUpCpJHtg0pR0W/b92A9NO17JS3BAQmHDn/yhmkir2p8llrKY9TOhleKIaSzy1JhxS3T9FVld6coA==", + "cpu": [ + "loong64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.5.tgz", + "integrity": "sha512-5ylkX6dWMeBKge9nTU+Rxfb+ZfaCIJ9lRqIFaK0eAMcWp7OJbYnLveLgXmm0VrvuLKb8qIK+mHyH0qu88RM+iA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.5.tgz", + "integrity": "sha512-oHK4ZHYFDKjZviK34I+NwgfbGxgI7ztrNxj2hPTSSNFgeq1a/lEd7dHV2fdGAuTH4Iym3RHJg+vAbWaWG4B7Zg==", + "cpu": [ + "ppc64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.5.tgz", + "integrity": "sha512-UcetmHZ6XOXuUByiKZyQmb55ZPr0LABr3Ec/HB9wKZn6CEAFWZkE+hsJErJ9hbPBC7nI0dKuELx7CoV6IM7TMg==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.5.tgz", + "integrity": "sha512-C5CmDPQBtvjVo8cgQsBs+w6WB0JLkiixhgi6hVLV11hERWdn/p0XcPU2OUcZzac9BPOFq7SbaHFa8r3SWEysCQ==", + "cpu": [ + "riscv64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.5.tgz", + "integrity": "sha512-lHVQHJFKsuuxLMi3MQO9XVL8Tje3JR82CzB+QDKC5NWBcsIWuwsn9uIM5e3lBhI+fF1/s63qnyYqsg65+8rV/w==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.5.tgz", + "integrity": "sha512-3W9bTFcQNJn71cSJVM9RKIiZOy8DO/XLDii8Uv/Pm6WKqDRj7JV3ZfuXIEfyuy5LXpIzAbB/1M4Ukp9GKNa7nA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.5.tgz", + "integrity": "sha512-VDC7rRJlee/scpki96GZ27Omf6yU87s1YXwVTpjE5841faVlDYYT565rgfmoR1U0sqL7z5ivQSDjcsF6VRXyBA==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.5.tgz", + "integrity": "sha512-z86Ok2p4pTdv5xqCKZsTooO7yBEiaJR/HzU3Wx8RmWsPoLppnMKROhJusQob8B3IE1ghC343kUW9rC2r+Wf3ig==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.5.tgz", + "integrity": "sha512-IzQmj+xXwQFGhMAMKMQVXkMwMZN3TqkJgAE0nSsqvVwWWciP4AIPMmWRqOQ2GfX7TUDZr+xqGFcBS36CRPGw0g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.5.tgz", + "integrity": "sha512-F6qpTaPc9bwBH85kjy0/BLmLSW1uv7AoOXCoRIkg2arlgCYlWYcAbiMkvZuAcaWk9TpCRG//okznLAqLGshkMw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.5.tgz", + "integrity": "sha512-igoDsTFhhwECBeGbUuLeIk7t8Y1apa+cs6mDWpx2EZ0ch7oEQgzHbFUXN9euoHekCAQzXdXApAGkV6jznS7tWw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.5.tgz", + "integrity": "sha512-U3teMeMbXFmaM5D+OTJpsOXd+wV/qftIeYF9kBKL4v73641qyJmoXFtA28DQLsnmlyayEsTe72xpLHrArq6vHw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.5.tgz", + "integrity": "sha512-ypfC34F3RKXvCXBglGqGMsUSMKlgwd1HX9AOAlx9RoZZ6GaI42YHVeKpzg3JG+wpBUJYTG+NNZhqbDWL8tBZkw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.21.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-x64": "4.3.0", + "@tailwindcss/oxide-freebsd-x64": "4.3.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-x64-musl": "4.3.0", + "@tailwindcss/oxide-wasm32-wasi": "4.3.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz", + "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "tailwindcss": "4.3.0" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.17.11", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.11.tgz", + "integrity": "sha512-+ILjvtHup6Y2hzQ6YzwMgX1Q+oQpxEGOXCEsCNaPoIP0VxMbizIBTmYTDtkerkIQS8/CbP1BRuyt8V/8BCsy1g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/vue-virtual": { + "version": "3.13.39", + "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.39.tgz", + "integrity": "sha512-AG8sI+6nfrDvN6ixuHJL9LaJTf3//0V5VITZYW3UOaIuSVexnEzsbMu4LH/JhMfRTxwhUXPnLqkl1DlzkZbRtg==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.17.11" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.0.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.3.tgz", + "integrity": "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.9.0" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.8.tgz", + "integrity": "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz", + "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz", + "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz", + "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz", + "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.7", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz", + "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz", + "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz", + "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.28" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue-macros/common": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.4.tgz", + "integrity": "sha512-/5Fv+6DgIcM9ajY05ZmKBv+LMX1M9A0X+IUwDRVdt67ciw8OV9bvG2r34p3RiEadlsQybjhKPRKNXDC8Bp23cw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-sfc": "^3.5.22", + "ast-kit": "^2.1.2", + "local-pkg": "^1.1.2", + "magic-string-ast": "^1.0.2", + "unplugin-utils": "^0.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/vue-macros" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.2.25" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + } + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.35.tgz", + "integrity": "sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/shared": "3.5.35", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.35.tgz", + "integrity": "sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.35", + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.35.tgz", + "integrity": "sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/compiler-core": "3.5.35", + "@vue/compiler-dom": "3.5.35", + "@vue/compiler-ssr": "3.5.35", + "@vue/shared": "3.5.35", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.35.tgz", + "integrity": "sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.35", + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/devtools-api": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.2.1.tgz", + "integrity": "sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^8.2.1" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.2.1.tgz", + "integrity": "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^8.2.1", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "perfect-debounce": "^2.0.0" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.2.1.tgz", + "integrity": "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==", + "license": "MIT" + }, + "node_modules/@vue/language-core": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.11.tgz", + "integrity": "sha512-QJmpliwAVpC/OxubIByPAhNzsQPRc8/gxlN2qnVzVfIMjMDz/9RnXRFoetjz5yEgXVXyp4LqhXq3V53PjmNzFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.2.1", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.4" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.35.tgz", + "integrity": "sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.35.tgz", + "integrity": "sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.35", + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.35.tgz", + "integrity": "sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.35", + "@vue/runtime-core": "3.5.35", + "@vue/shared": "3.5.35", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.35.tgz", + "integrity": "sha512-NkebSOYdB97wi8OQcO3HqzZSlymJi/aWsN/7h74OSVhRTm6qGs3Jp3e0rCXynmWwSlKeRrnlIug+ilYoHBmQDA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.35", + "@vue/shared": "3.5.35" + }, + "peerDependencies": { + "vue": "3.5.35" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.35.tgz", + "integrity": "sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==", + "license": "MIT" + }, + "node_modules/@vue/test-utils": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.11.tgz", + "integrity": "sha512-GDqaqZsA6m2E5vNzej0aYiIb6BX8xV9pNSbbbXKOfEYwg7ZNblVX8suyqmUBThq8VIrgAJNxn+z72hVtUeiWHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-beautify": "^1.14.9", + "vue-component-type-helpers": "^3.0.0" + }, + "peerDependencies": { + "@vue/compiler-dom": "3.x", + "@vue/server-renderer": "3.x", + "vue": "3.x" + }, + "peerDependenciesMeta": { + "@vue/server-renderer": { + "optional": true + } + } + }, + "node_modules/@vueuse/core": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.4.0.tgz", + "integrity": "sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "14.4.0", + "@vueuse/shared": "14.4.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/metadata": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.4.0.tgz", + "integrity": "sha512-swx/255R6JyHZFJhx845iz5CRWDZdCfvkZOpACWc5+c5WHcG24mv8gUT1WIdFQaHt6dq79rvILd9QnCWiyVm9g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.4.0.tgz", + "integrity": "sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/alien-signals": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz", + "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-kit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz", + "integrity": "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "pathe": "^2.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/ast-walker-scope": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.9.0.tgz", + "integrity": "sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@babel/types": "^7.29.0", + "ast-kit": "^2.2.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/buffer-image-size": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/buffer-image-size/-/buffer-image-size-0.6.4.tgz", + "integrity": "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/confbox": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.1.tgz", + "integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==", + "license": "MIT" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/editorconfig": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.7.tgz", + "integrity": "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "^9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.25.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.25.1.tgz", + "integrity": "sha512-nGXts5znJzmWPu+mIE9izCOzdg63oJca2mDzGWWTth7sr4aCToKcoyFVBQwN75Ij5Pf6p510EwkTqViTRzDV+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/exsolve": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz", + "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/happy-dom": { + "version": "20.11.6", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.11.6.tgz", + "integrity": "sha512-Hldbg8AdAa5a5oDcZpjqnGitp7JB0hqWmfv/8qr+kft4vzSD8BHsbdRfzYvL/0QcbKcURC/yyoygbeDQarPvYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": ">=20.0.0", + "@types/whatwg-mimetype": "^3.0.2", + "@types/ws": "^8.18.1", + "buffer-image-size": "^0.6.4", + "entities": "^7.0.1", + "whatwg-mimetype": "^3.0.0", + "ws": "^8.21.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-cookie": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz", + "integrity": "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "devOptional": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/local-pkg": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz", + "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magic-string-ast": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-1.0.3.tgz", + "integrity": "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==", + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.19" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ohash": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", + "license": "MIT" + }, + "node_modules/openapi-fetch": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.17.0.tgz", + "integrity": "sha512-PsbZR1wAPcG91eEthKhN+Zn92FMHxv+/faECIwjXdxfTODGSGegYv0sc1Olz+HYPvKOuoXfp+0pA2XVt2cI0Ig==", + "license": "MIT", + "dependencies": { + "openapi-typescript-helpers": "^0.1.0" + } + }, + "node_modules/openapi-typescript": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.13.0.tgz", + "integrity": "sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/openapi-core": "^1.34.6", + "ansi-colors": "^4.1.3", + "change-case": "^5.4.4", + "parse-json": "^8.3.0", + "supports-color": "^10.2.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "openapi-typescript": "bin/cli.js" + }, + "peerDependencies": { + "typescript": "^5.x" + } + }, + "node_modules/openapi-typescript-helpers": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.1.0.tgz", + "integrity": "sha512-OKTGPthhivLw/fHz6c3OPtg72vi86qaMlqbJuVJ23qOvQ+53uw1n7HdmkJFibloF7QEjDrDkzJiOJuockM/ljw==", + "license": "MIT" + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.3.tgz", + "integrity": "sha512-j/lCFdcppV0JxWpCEITdbDltBxPP6cHT+yNJ6Go2OgoSA9518X847X9z0p6LtA4Nc16+eQzCZjRrWanTGvHJ5w==", + "license": "MIT", + "dependencies": { + "confbox": "^0.3.1", + "exsolve": "^1.1.1", + "pathe": "^2.0.3" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/reka-ui": { + "version": "2.9.10", + "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.9.10.tgz", + "integrity": "sha512-yuvZVTp4fWH2G3qk+ze/x6YYlyc2Xl1d+eMUlIYrKqzTowBKteoDoN17fitURmqSUck3mc7JbcYgp49DnGu2EQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.13", + "@floating-ui/vue": "^1.1.6", + "@internationalized/date": "^3.5.0", + "@internationalized/number": "^3.5.0", + "@tanstack/vue-virtual": "^3.12.0", + "@vueuse/core": "^14.1.0", + "@vueuse/shared": "^14.1.0", + "aria-hidden": "^1.2.4", + "defu": "^6.1.5", + "ohash": "^2.0.11" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/zernonia" + }, + "peerDependencies": { + "vue": ">= 3.4.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.5.tgz", + "integrity": "sha512-KRWwmNLlPw5M7HcdYfm15oBv9n9LPtjzpzCIxS/phwqvPyxHSoKX6Y2YU3pxSPfy0CLquVgsx/j/hBi6OvH1Nw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.5", + "@rollup/rollup-android-arm64": "4.63.5", + "@rollup/rollup-darwin-arm64": "4.63.5", + "@rollup/rollup-darwin-x64": "4.63.5", + "@rollup/rollup-freebsd-arm64": "4.63.5", + "@rollup/rollup-freebsd-x64": "4.63.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.5", + "@rollup/rollup-linux-arm-musleabihf": "4.63.5", + "@rollup/rollup-linux-arm64-gnu": "4.63.5", + "@rollup/rollup-linux-arm64-musl": "4.63.5", + "@rollup/rollup-linux-loong64-gnu": "4.63.5", + "@rollup/rollup-linux-loong64-musl": "4.63.5", + "@rollup/rollup-linux-ppc64-gnu": "4.63.5", + "@rollup/rollup-linux-ppc64-musl": "4.63.5", + "@rollup/rollup-linux-riscv64-gnu": "4.63.5", + "@rollup/rollup-linux-riscv64-musl": "4.63.5", + "@rollup/rollup-linux-s390x-gnu": "4.63.5", + "@rollup/rollup-linux-x64-gnu": "4.63.5", + "@rollup/rollup-linux-x64-musl": "4.63.5", + "@rollup/rollup-openbsd-x64": "4.63.5", + "@rollup/rollup-openharmony-arm64": "4.63.5", + "@rollup/rollup-win32-arm64-msvc": "4.63.5", + "@rollup/rollup-win32-ia32-msvc": "4.63.5", + "@rollup/rollup-win32-x64-gnu": "4.63.5", + "@rollup/rollup-win32-x64-msvc": "4.63.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.6.tgz", + "integrity": "sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/unplugin": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.4.0.tgz", + "integrity": "sha512-9skdIFlCsPdFV7wUfZxNsFInlW+7nJmGu2gkTu0OUhF56aXGsHab9x52/QhdJ4lC7ZDPWTxbiC4ANqVlsuaW3w==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "picomatch": "^4.0.7", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@farmfe/core": "*", + "@rsbuild/core": "*", + "@rspack/core": "*", + "bun-types-no-globals": "*", + "esbuild": "*", + "rolldown": "*", + "rollup": "*", + "unloader": "*", + "vite": "*", + "webpack": "*" + }, + "peerDependenciesMeta": { + "@farmfe/core": { + "optional": true + }, + "@rsbuild/core": { + "optional": true + }, + "@rspack/core": { + "optional": true + }, + "bun-types-no-globals": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + }, + "unloader": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/unplugin-utils": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.2.tgz", + "integrity": "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==", + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/uri-js-replace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz", + "integrity": "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.5.tgz", + "integrity": "sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "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 + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz", + "integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.7", + "@vitest/mocker": "3.2.7", + "@vitest/pretty-format": "^3.2.7", + "@vitest/runner": "3.2.7", + "@vitest/snapshot": "3.2.7", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.7", + "@vitest/ui": "3.2.7", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.2.0.tgz", + "integrity": "sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.35.tgz", + "integrity": "sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.35", + "@vue/compiler-sfc": "3.5.35", + "@vue/runtime-dom": "3.5.35", + "@vue/server-renderer": "3.5.35", + "@vue/shared": "3.5.35" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.11.tgz", + "integrity": "sha512-LwcxzeliO9fkQcpJG0PoX8X5kmAhKmH9wkpDLxNabwzkQ9Zeib2YVHwFV4pcWmMLfXVfjr/dSV+DaJ3cIPgSNA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue-router": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.1.0.tgz", + "integrity": "sha512-HAbiLzLEHQwxPgvsbOJDAwtavszEgLwri6XfyrsPECIFez8+59xc9LofWVdc/HEaSRT822lJ8H9Ns38VVond5g==", + "license": "MIT", + "dependencies": { + "@babel/generator": "^8.0.0-rc.4", + "@vue-macros/common": "^3.1.1", + "@vue/devtools-api": "^8.1.2", + "ast-walker-scope": "^0.9.0", + "chokidar": "^5.0.0", + "json5": "^2.2.3", + "local-pkg": "^1.1.2", + "magic-string": "^0.30.21", + "mlly": "^1.8.2", + "muggle-string": "^0.4.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "scule": "^1.3.0", + "tinyglobby": "^0.2.16", + "unplugin": "^3.0.0", + "unplugin-utils": "^0.3.1", + "yaml": "^2.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@pinia/colada": ">=0.21.2", + "@vue/compiler-sfc": "^3.5.34", + "pinia": "^3.0.4", + "vite": "^7.0.0 || ^8.0.0", + "vue": "^3.5.34" + }, + "peerDependenciesMeta": { + "@pinia/colada": { + "optional": true + }, + "@vue/compiler-sfc": { + "optional": true + }, + "pinia": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vue-tsc": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.11.tgz", + "integrity": "sha512-gOb0B9rtU2+f1dszwPqSH5kAieIF9ReeLhD3kSRNHv5WZZUQz/JdVXW0RTdqhNTMlQkqKzrTTviqKr/4FYZraQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.28", + "@vue/language-core": "3.3.11" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "license": "MIT" + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", + "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yaml-ast-parser": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", + "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/admin/package.json b/admin/package.json new file mode 100644 index 0000000..c910fa3 --- /dev/null +++ b/admin/package.json @@ -0,0 +1,36 @@ +{ + "name": "summercms-admin", + "private": true, + "type": "module", + "engines": { + "node": ">=22.6" + }, + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build", + "typecheck": "vue-tsc --noEmit", + "test": "vitest run", + "gen:api": "openapi-typescript openapi/admin.json -o src/api/schema.d.ts" + }, + "dependencies": { + "@fontsource/dm-mono": "5.3.0", + "@fontsource/dm-sans": "5.3.0", + "@lucide/vue": "1.17.0", + "openapi-fetch": "0.17.0", + "reka-ui": "2.9.10", + "vue": "3.5.35", + "vue-router": "5.1.0" + }, + "devDependencies": { + "@tailwindcss/vite": "4.3.0", + "@vitejs/plugin-vue": "6.0.8", + "@vue/test-utils": "2.4.11", + "happy-dom": "20.11.6", + "openapi-typescript": "7.13.0", + "tailwindcss": "4.3.0", + "typescript": "5.9.3", + "vite": "7.3.5", + "vitest": "3.2.7", + "vue-tsc": "3.3.11" + } +} diff --git a/admin/src/App.vue b/admin/src/App.vue new file mode 100644 index 0000000..a63b032 --- /dev/null +++ b/admin/src/App.vue @@ -0,0 +1,15 @@ + + + diff --git a/admin/src/api/client.ts b/admin/src/api/client.ts new file mode 100644 index 0000000..f0489c2 --- /dev/null +++ b/admin/src/api/client.ts @@ -0,0 +1,44 @@ +// The only HTTP client of the SPA: openapi-fetch typed by the generated +// paths (D-15). Every request carries X-Requested-With (the CSRF header the +// admin API requires on state-changing cookie requests, D-19) and same-origin +// credentials; the JWT lives in an HttpOnly cookie the SPA never reads. +import createClient, { type Middleware } from 'openapi-fetch' +import type { paths } from './schema' +import { runtime } from '../app/runtime' + +export const REQUESTED_WITH = 'XMLHttpRequest' + +type UnauthorizedHandler = () => void + +let unauthorizedHandler: UnauthorizedHandler | null = null + +/** Registers what happens when an API call other than login returns 401. */ +export function onUnauthorized(handler: UnauthorizedHandler | null): void { + unauthorizedHandler = handler +} + +function isLoginRequest(request: Request): boolean { + return new URL(request.url, 'http://local').pathname.endsWith('/auth/login') +} + +export const transport: Middleware = { + onRequest({ request }) { + request.headers.set('X-Requested-With', REQUESTED_WITH) + return request + }, + onResponse({ request, response }) { + if (response.status === 401 && !isLoginRequest(request)) { + unauthorizedHandler?.() + } + return response + }, +} + +export const api = createClient({ + baseUrl: runtime.api, + credentials: 'same-origin', + // Resolve fetch per call so tests can replace globalThis.fetch. + fetch: (request: Request) => globalThis.fetch(request), +}) + +api.use(transport) diff --git a/admin/src/api/schema.d.ts b/admin/src/api/schema.d.ts new file mode 100644 index 0000000..ad07c17 --- /dev/null +++ b/admin/src/api/schema.d.ts @@ -0,0 +1,1532 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/auth/login": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Admin login */ + post: { + parameters: { + query?: never; + header?: { + /** @description XMLHttpRequest selects cookie transport */ + "X-Requested-With"?: string; + }; + path?: never; + cookie?: never; + }; + /** @description Credentials */ + requestBody: { + content: { + "application/json": components["schemas"]["cabana.AdminLoginRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.Envelope-cabana_AdminLoginData"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/logout": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Admin logout */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.SuccessEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/me": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Current admin */ + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.Envelope-cabana_AdminProfile"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/refresh": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Refresh an admin token */ + post: { + parameters: { + query?: never; + header?: { + /** @description XMLHttpRequest; required unless a Bearer token is sent */ + "X-Requested-With"?: string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.Envelope-cabana_AdminLoginData"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/navigation": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Admin navigation */ + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.Envelope-array_cabana_NavigationEntry"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/settings": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List admin settings */ + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.SuccessEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/settings/{code}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Read admin settings */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Settings code */ + code: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.SuccessEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + /** Update admin settings */ + put: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Settings code */ + code: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.SuccessEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Unprocessable Entity */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/settings/{code}/schema": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Admin settings schema */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Settings code */ + code: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.SuccessEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/{vendor}/{plugin}/{controller}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List admin records */ + get: { + parameters: { + query?: { + /** @description Search term */ + search?: string; + /** @description Sort column */ + sort?: string; + /** @description Sort direction (asc or desc) */ + dir?: string; + /** @description Page */ + page?: number; + /** @description Records per page */ + per_page?: number; + }; + header?: never; + path: { + /** @description Vendor */ + vendor: string; + /** @description Plugin */ + plugin: string; + /** @description Controller */ + controller: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ListEnvelope-array_cabana_AdminRecord"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Unprocessable Entity */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + put?: never; + /** Create an admin record */ + post: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Vendor */ + vendor: string; + /** @description Plugin */ + plugin: string; + /** @description Controller */ + controller: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.SuccessEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Unprocessable Entity */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/{vendor}/{plugin}/{controller}/bulk-delete": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Bulk-delete admin records */ + post: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Vendor */ + vendor: string; + /** @description Plugin */ + plugin: string; + /** @description Controller */ + controller: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.SuccessEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Unprocessable Entity */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/{vendor}/{plugin}/{controller}/schema/form": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Admin form schema */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Vendor */ + vendor: string; + /** @description Plugin */ + plugin: string; + /** @description Controller */ + controller: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.SuccessEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/{vendor}/{plugin}/{controller}/schema/list": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Admin list schema */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Vendor */ + vendor: string; + /** @description Plugin */ + plugin: string; + /** @description Controller */ + controller: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.Envelope-cabana_ListSchema"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/{vendor}/{plugin}/{controller}/schema/relation/{name}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Admin relation schema */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Vendor */ + vendor: string; + /** @description Plugin */ + plugin: string; + /** @description Controller */ + controller: string; + /** @description Relation name */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.SuccessEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/{vendor}/{plugin}/{controller}/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Show an admin record */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Vendor */ + vendor: string; + /** @description Plugin */ + plugin: string; + /** @description Controller */ + controller: string; + /** @description Record id */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.SuccessEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + /** Update an admin record */ + put: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Vendor */ + vendor: string; + /** @description Plugin */ + plugin: string; + /** @description Controller */ + controller: string; + /** @description Record id */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.SuccessEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Unprocessable Entity */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + post?: never; + /** Delete an admin record */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Vendor */ + vendor: string; + /** @description Plugin */ + plugin: string; + /** @description Controller */ + controller: string; + /** @description Record id */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.SuccessEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/{vendor}/{plugin}/{controller}/{id}/relations/{name}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List linked relation records */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Vendor */ + vendor: string; + /** @description Plugin */ + plugin: string; + /** @description Controller */ + controller: string; + /** @description Owner id */ + id: number; + /** @description Relation name */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.SuccessEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/{vendor}/{plugin}/{controller}/{id}/relations/{name}/candidates": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List relation candidates */ + get: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Vendor */ + vendor: string; + /** @description Plugin */ + plugin: string; + /** @description Controller */ + controller: string; + /** @description Owner id */ + id: number; + /** @description Relation name */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.SuccessEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/{vendor}/{plugin}/{controller}/{id}/relations/{name}/link": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Link relation records */ + post: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Vendor */ + vendor: string; + /** @description Plugin */ + plugin: string; + /** @description Controller */ + controller: string; + /** @description Owner id */ + id: number; + /** @description Relation name */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.SuccessEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Unprocessable Entity */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/{vendor}/{plugin}/{controller}/{id}/relations/{name}/unlink": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Unlink relation records */ + post: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Vendor */ + vendor: string; + /** @description Plugin */ + plugin: string; + /** @description Controller */ + controller: string; + /** @description Owner id */ + id: number; + /** @description Relation name */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.SuccessEnvelope"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + /** @description Unprocessable Entity */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["cabana.ErrorEnvelope"]; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + "cabana.AdminLoginData": { + access_token?: string; + expires_in?: number; + token_type: string; + }; + "cabana.AdminLoginRequest": { + email?: string; + login?: string; + password: string; + }; + "cabana.AdminProfile": { + email: string; + first_name: string; + id: number; + is_superuser: boolean; + last_name: string; + login: string; + role?: components["schemas"]["cabana.AdminRoleSummary"]; + }; + "cabana.AdminRoleSummary": { + code: string; + id: number; + name: string; + }; + "cabana.BulkAction": { + label?: string; + name: string; + }; + "cabana.Envelope-array_cabana_NavigationEntry": { + data: components["schemas"]["cabana.NavigationEntry"][]; + meta: components["schemas"]["cabana.SuccessMeta"]; + }; + "cabana.Envelope-cabana_AdminLoginData": { + data: components["schemas"]["cabana.AdminLoginData"]; + meta: components["schemas"]["cabana.SuccessMeta"]; + }; + "cabana.Envelope-cabana_AdminProfile": { + data: components["schemas"]["cabana.AdminProfile"]; + meta: components["schemas"]["cabana.SuccessMeta"]; + }; + "cabana.Envelope-cabana_ListSchema": { + data: components["schemas"]["cabana.ListSchema"]; + meta: components["schemas"]["cabana.SuccessMeta"]; + }; + "cabana.ErrorBody": { + code: string; + details: { + [key: string]: unknown; + }; + message: string; + }; + "cabana.ErrorEnvelope": { + error: components["schemas"]["cabana.ErrorBody"]; + }; + "cabana.FormMeta": { + locale: string; + }; + "cabana.FormOption": { + label: string; + value: components["schemas"]["cabana.jsonScalar"]; + }; + "cabana.ListColumn": { + key: string; + label: string; + relation?: string; + searchable: boolean; + select?: string; + sortable: boolean; + type?: string; + }; + "cabana.ListEnvelope-array_cabana_AdminRecord": { + data: { + [key: string]: unknown; + }[]; + meta: components["schemas"]["cabana.ListMeta"]; + }; + "cabana.ListFilter": { + column?: string; + falseValue?: components["schemas"]["cabana.jsonScalar"]; + label?: string; + modelClass?: string; + name: string; + nameFrom?: string; + options?: components["schemas"]["cabana.FormOption"][]; + scope?: string; + trueValue?: components["schemas"]["cabana.jsonScalar"]; + type: string; + }; + "cabana.ListMeta": { + last_page: number; + page: number; + per_page: number; + total: number; + }; + "cabana.ListSchema": { + bulkActions: components["schemas"]["cabana.BulkAction"][]; + columns: components["schemas"]["cabana.ListColumn"][]; + defaultSort?: components["schemas"]["cabana.ListSort"]; + filters: components["schemas"]["cabana.ListFilter"][]; + meta?: components["schemas"]["cabana.FormMeta"]; + modelClass?: string; + noRecordsMessage?: string; + perPageOptions: number[]; + recordUrl?: string; + recordsPerPage: number; + rowActions: components["schemas"]["cabana.RowAction"][]; + searchPrompt?: string; + searchTerm: string; + showCheckboxes: boolean; + showSearch: boolean; + showSetup: boolean; + showSorting: boolean; + title?: string; + toolbarButtons: string[]; + }; + "cabana.ListSort": { + column: string; + direction: string; + }; + "cabana.NavigationEntry": { + code: string; + controller: string; + icon: string; + label: string; + order: number; + sideMenu: components["schemas"]["cabana.NavigationEntry"][]; + }; + "cabana.RowAction": { + label?: string; + name: string; + url?: string; + }; + "cabana.SuccessEnvelope": { + data: unknown; + meta: components["schemas"]["cabana.SuccessMeta"]; + }; + "cabana.SuccessMeta": { + last_page?: number; + locale?: string; + page?: number; + per_page?: number; + total?: number; + }; + "cabana.jsonScalar": (string | number | boolean) | null; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export type operations = Record; diff --git a/admin/src/api/types.ts b/admin/src/api/types.ts new file mode 100644 index 0000000..de63f05 --- /dev/null +++ b/admin/src/api/types.ts @@ -0,0 +1,16 @@ +// Aliases onto the generated OpenAPI schema (D-15, D-16). No API shape is +// written by hand: every type here points at components['schemas']. +import type { components } from './schema' + +type Schemas = components['schemas'] + +export type AdminLoginData = Schemas['cabana.AdminLoginData'] +export type AdminLoginRequest = Schemas['cabana.AdminLoginRequest'] +export type AdminProfile = Schemas['cabana.AdminProfile'] +export type NavigationEntry = Schemas['cabana.NavigationEntry'] +export type ListSchema = Schemas['cabana.ListSchema'] +export type ListColumn = Schemas['cabana.ListColumn'] +export type ListMeta = Schemas['cabana.ListMeta'] +export type ErrorEnvelope = Schemas['cabana.ErrorEnvelope'] +/** One record: a string-keyed map read through its list or form schema. */ +export type AdminRecord = Schemas['cabana.ListEnvelope-array_cabana_AdminRecord']['data'][number] diff --git a/admin/src/app/controllerRoutes.ts b/admin/src/app/controllerRoutes.ts new file mode 100644 index 0000000..03b0056 --- /dev/null +++ b/admin/src/app/controllerRoutes.ts @@ -0,0 +1,42 @@ +// Controller IDs map one to one onto SPA paths (D-10): +// vendor.plugin.controller <-> /vendor/plugin/controller. +export interface ControllerParams { + vendor: string + plugin: string + controller: string +} + +const SEGMENT = /^[A-Za-z0-9_-]+$/ + +export function parseControllerId(id: string): ControllerParams | null { + const parts = id.split('.') + if (parts.length !== 3 || !parts.every((part) => SEGMENT.test(part))) { + return null + } + const [vendor, plugin, controller] = parts as [string, string, string] + return { vendor, plugin, controller } +} + +export function controllerPath(id: string): string | null { + const params = parseControllerId(id) + return params ? `/${params.vendor}/${params.plugin}/${params.controller}` : null +} + +export function controllerIdFromParams(params: ControllerParams): string { + return `${params.vendor}.${params.plugin}.${params.controller}` +} + +export function controllerIdFromPath(path: string): string | null { + const parts = path.replace(/^\/+|\/+$/g, '').split('/') + if (parts.length < 3) { + return null + } + const id = parts.slice(0, 3).join('.') + return parseControllerId(id) ? id : null +} + +/** vendor.plugin prefix of a controller ID, used to find the owning plugin. */ +export function pluginKey(id: string): string | null { + const params = parseControllerId(id) + return params ? `${params.vendor}.${params.plugin}` : null +} diff --git a/admin/src/app/i18n.ts b/admin/src/app/i18n.ts new file mode 100644 index 0000000..03ad1b3 --- /dev/null +++ b/admin/src/app/i18n.ts @@ -0,0 +1,42 @@ +// UI strings (D-20). The resolved backend::lang bundle is loaded from the +// server in a later plan; until a key is loaded, t() returns the key itself, +// mirroring phrasebook's missing-key fallback. Placeholders use phrasebook's +// :name syntax with :Name and :NAME casing variants (D-24). +import { ref } from 'vue' + +type Forms = Record + +const bundle = ref>({}) + +export function setBundle(next: Record): void { + bundle.value = next +} + +export function interpolate(text: string, params: Record = {}): string { + const replacements: Array<[string, string]> = [] + for (const [rawName, raw] of Object.entries(params)) { + const name = rawName.replace(/^:/, '') + if (name === '') { + continue + } + const value = String(raw) + replacements.push([`:${name.charAt(0).toUpperCase()}${name.slice(1)}`, value.charAt(0).toUpperCase() + value.slice(1)]) + replacements.push([`:${name.toUpperCase()}`, value.toUpperCase()]) + replacements.push([`:${name}`, value]) + } + replacements.sort((a, b) => b[0].length - a[0].length) + let out = text + for (const [placeholder, value] of replacements) { + out = out.split(placeholder).join(value) + } + return out +} + +export function t(key: string, params: Record = {}): string { + const forms = bundle.value[key] + const text = forms?.other + if (text === undefined) { + return key + } + return interpolate(text, params) +} diff --git a/admin/src/app/icons.ts b/admin/src/app/icons.ts new file mode 100644 index 0000000..b1d8255 --- /dev/null +++ b/admin/src/app/icons.ts @@ -0,0 +1,111 @@ +// Navigation icons (D-11). The registry stores lucide names; ported Winter +// plugins may still send icon-* names, which map onto lucide equivalents. +// Named imports only: a namespace import would bundle every lucide icon. +import type { Component } from 'vue' +import { + Archive, + ArrowDown, + ArrowLeft, + ArrowRight, + ArrowUp, + ArrowUpRight, + CassetteTape, + Check, + ChevronDown, + ChevronLeft, + ChevronRight, + Circle, + CircleAlert, + Disc, + Disc3, + Image, + Library, + List, + LogOut, + MicVocal, + Minus, + Palette, + PanelLeftClose, + PanelLeftOpen, + Plus, + Puzzle, + Search, + SearchX, + Settings, + ShieldCheck, + Square, + Sun, + Tags, + Trash2, + User, + UserMinus, + UserPlus, + Users, + UsersRound, + X, +} from '@lucide/vue' + +export const icons: Readonly> = { + archive: Archive, + 'arrow-down': ArrowDown, + 'arrow-left': ArrowLeft, + 'arrow-right': ArrowRight, + 'arrow-up': ArrowUp, + 'arrow-up-right': ArrowUpRight, + 'cassette-tape': CassetteTape, + check: Check, + 'chevron-down': ChevronDown, + 'chevron-left': ChevronLeft, + 'chevron-right': ChevronRight, + circle: Circle, + 'circle-alert': CircleAlert, + disc: Disc, + 'disc-3': Disc3, + image: Image, + library: Library, + list: List, + 'log-out': LogOut, + 'mic-vocal': MicVocal, + minus: Minus, + palette: Palette, + 'panel-left-close': PanelLeftClose, + 'panel-left-open': PanelLeftOpen, + plus: Plus, + puzzle: Puzzle, + search: Search, + 'search-x': SearchX, + settings: Settings, + 'shield-check': ShieldCheck, + sun: Sun, + tags: Tags, + 'trash-2': Trash2, + user: User, + 'user-minus': UserMinus, + 'user-plus': UserPlus, + users: Users, + 'users-round': UsersRound, + x: X, +} + +/** Winter backend icon classes mapped to lucide names. */ +export const winterIcons: Readonly> = { + 'icon-archive': 'archive', + 'icon-circle': 'circle', + 'icon-list-ul': 'list', + 'icon-tags': 'tags', + 'icon-user': 'user', + 'icon-search': 'search', + 'icon-cog': 'settings', + 'icon-users': 'users', +} + +/** Neutral icon for names neither lucide map knows. */ +export const fallbackIcon: Component = Square + +export function iconFor(name: string | null | undefined): Component { + if (!name) { + return fallbackIcon + } + const key = name.trim() + return icons[key] ?? icons[winterIcons[key] ?? ''] ?? fallbackIcon +} diff --git a/admin/src/app/router.ts b/admin/src/app/router.ts new file mode 100644 index 0000000..f8440cd --- /dev/null +++ b/admin/src/app/router.ts @@ -0,0 +1,63 @@ +import { createRouter, createWebHistory, type RouterHistory } from 'vue-router' +import { runtime } from './runtime' +import { currentUser } from '../state/useAuth' +import { homePath } from '../state/useNavigation' +import LoginView from '../views/LoginView.vue' +import ListView from '../views/ListView.vue' +import NotFoundView from '../views/NotFoundView.vue' + +declare module 'vue-router' { + interface RouteMeta { + /** Reachable without a session. */ + public?: boolean + /** Rendered inside the navigation shell. */ + shell?: boolean + } +} + +/** + * Accepts a post-login redirect only when it is an in-app path: it must start + * with exactly one slash, which rejects absolute and protocol-relative URLs. + */ +export function safeRedirect(value: unknown): string | null { + if (typeof value !== 'string' || value.length === 0) { + return null + } + if (!value.startsWith('/') || value.startsWith('//') || value.startsWith('/\\')) { + return null + } + return value +} + +export function createAdminRouter(history: RouterHistory = createWebHistory(runtime.base)) { + const router = createRouter({ + history, + routes: [ + { path: '/login', name: 'login', component: LoginView, meta: { public: true } }, + { + path: '/', + name: 'home', + component: NotFoundView, + props: { home: true }, + meta: { shell: true }, + beforeEnter: () => homePath() ?? true, + }, + { path: '/:vendor/:plugin/:controller', name: 'list', component: ListView, meta: { shell: true } }, + { path: '/:pathMatch(.*)*', name: 'not-found', component: NotFoundView, meta: { shell: true } }, + ], + }) + router.beforeEach((to) => { + const signedIn = currentUser.value !== null + if (to.meta.public) { + if (signedIn && to.name === 'login') { + return safeRedirect(to.query.redirect) ?? '/' + } + return true + } + if (!signedIn) { + return { name: 'login', query: { redirect: to.fullPath } } + } + return true + }) + return router +} diff --git a/admin/src/app/runtime.ts b/admin/src/app/runtime.ts new file mode 100644 index 0000000..313fc8d --- /dev/null +++ b/admin/src/app/runtime.ts @@ -0,0 +1,23 @@ +// Runtime configuration read once from the served index.html. The Go server +// (boardwalk) writes the configured backend.uri into the summer-admin-base +// meta; the SPA derives its router base and API base from it (D-02, D-03). +export const DEFAULT_BASE = '/backend' +const TOKEN = '__SUMMER_ADMIN_BASE__' + +export function readBase(doc: Document = document): string { + const content = doc.querySelector('meta[name="summer-admin-base"]')?.content.trim() ?? '' + if (content === '' || content === TOKEN || !content.startsWith('/')) { + return DEFAULT_BASE + } + const trimmed = content.replace(/\/+$/, '') + return trimmed === '' ? DEFAULT_BASE : trimmed +} + +const base = readBase() + +export const runtime = { + /** Admin mount path, for example /backend. */ + base, + /** Admin API root, the mount path plus /api/v1. */ + api: `${base}/api/v1`, +} as const diff --git a/admin/src/components/list/DataTable.vue b/admin/src/components/list/DataTable.vue new file mode 100644 index 0000000..2ded6d8 --- /dev/null +++ b/admin/src/components/list/DataTable.vue @@ -0,0 +1,85 @@ + + + diff --git a/admin/src/components/shell/AppShell.vue b/admin/src/components/shell/AppShell.vue new file mode 100644 index 0000000..26066a6 --- /dev/null +++ b/admin/src/components/shell/AppShell.vue @@ -0,0 +1,72 @@ + + + diff --git a/admin/src/components/shell/PluginRail.vue b/admin/src/components/shell/PluginRail.vue new file mode 100644 index 0000000..f08ea68 --- /dev/null +++ b/admin/src/components/shell/PluginRail.vue @@ -0,0 +1,43 @@ + + + diff --git a/admin/src/components/shell/SectionPanel.vue b/admin/src/components/shell/SectionPanel.vue new file mode 100644 index 0000000..1035d34 --- /dev/null +++ b/admin/src/components/shell/SectionPanel.vue @@ -0,0 +1,45 @@ + + + diff --git a/admin/src/main.ts b/admin/src/main.ts new file mode 100644 index 0000000..0b1925f --- /dev/null +++ b/admin/src/main.ts @@ -0,0 +1,30 @@ +import { createApp } from 'vue' +import App from './App.vue' +import { createAdminRouter } from './app/router' +import { onUnauthorized } from './api/client' +import { clearUser, me } from './state/useAuth' +import { loadNavigation } from './state/useNavigation' +import './styles/main.css' + +async function boot(): Promise { + // A 401 here only means "not signed in"; the router guard sends the + // visitor to the login route with the requested path as redirect. + const user = await me().catch(() => null) + if (user) { + await loadNavigation().catch(() => []) + } + const router = createAdminRouter() + onUnauthorized(() => { + clearUser() + const current = router.currentRoute.value + if (current.name !== 'login') { + void router.push({ name: 'login', query: { redirect: current.fullPath } }) + } + }) + const app = createApp(App) + app.use(router) + await router.isReady() + app.mount('#app') +} + +void boot() diff --git a/admin/src/state/useAuth.ts b/admin/src/state/useAuth.ts new file mode 100644 index 0000000..cf7b4ce --- /dev/null +++ b/admin/src/state/useAuth.ts @@ -0,0 +1,44 @@ +// Admin session state. The JWT travels in an HttpOnly cookie (D-19): the SPA +// keeps only the profile and the access lifetime, never a token. +import { readonly, ref } from 'vue' +import { api } from '../api/client' +import type { AdminProfile } from '../api/types' + +const user = ref(null) +const expiresIn = ref(null) + +export const currentUser = readonly(user) + +/** Logs in over cookie transport. Returns false on invalid credentials. */ +export async function login(identifier: string, password: string): Promise { + const { data, response } = await api.POST('/auth/login', { + body: { login: identifier, password }, + }) + if (!response.ok || !data) { + return false + } + expiresIn.value = typeof data.data.expires_in === 'number' ? data.data.expires_in : null + return true +} + +/** Loads the signed-in admin; null when the session is missing or expired. */ +export async function me(): Promise { + const { data, response } = await api.GET('/auth/me') + user.value = response.ok && data ? data.data : null + return user.value +} + +export function clearUser(): void { + user.value = null + expiresIn.value = null +} + +export function useAuth() { + return { + user: currentUser, + expiresIn: readonly(expiresIn), + login, + me, + clearUser, + } +} diff --git a/admin/src/state/useNavigation.ts b/admin/src/state/useNavigation.ts new file mode 100644 index 0000000..d5bb45d --- /dev/null +++ b/admin/src/state/useNavigation.ts @@ -0,0 +1,65 @@ +// Server-filtered navigation (D-10). The server removes items the admin may +// not open; the rail also omits a plugin whose side menu ends up empty. +import { computed, readonly, ref } from 'vue' +import { api } from '../api/client' +import type { NavigationEntry } from '../api/types' +import { controllerPath, pluginKey } from '../app/controllerRoutes' + +const entries = ref([]) + +export const navigation = readonly(entries) + +export async function loadNavigation(): Promise { + const { data, response } = await api.GET('/navigation') + entries.value = response.ok && data ? data.data : [] + return entries.value +} + +export function setNavigation(next: NavigationEntry[]): void { + entries.value = next +} + +/** Rail entries: plugins with at least one permitted side-menu item, by order. */ +export const railEntries = computed(() => + entries.value + .filter((entry) => entry.sideMenu.length > 0) + .map((entry, index) => ({ entry, index })) + .sort((a, b) => a.entry.order - b.entry.order || a.index - b.index) + .map(({ entry }) => entry), +) + +/** Path of the plugin's first permitted side-menu controller. */ +export function firstControllerPath(entry: NavigationEntry): string | null { + for (const item of entry.sideMenu) { + const path = controllerPath(item.controller) + if (path) { + return path + } + } + return controllerPath(entry.controller) +} + +/** The rail entry owning a route's vendor and plugin segments. */ +export function activeEntry(vendor: string, plugin: string): NavigationEntry | null { + const key = `${vendor}.${plugin}` + return ( + railEntries.value.find( + (entry) => pluginKey(entry.controller) === key || entry.sideMenu.some((item) => pluginKey(item.controller) === key), + ) ?? null + ) +} + +/** Where "/" lands: the first plugin's first controller. */ +export function homePath(): string | null { + for (const entry of railEntries.value) { + const path = firstControllerPath(entry) + if (path) { + return path + } + } + return null +} + +export function useNavigation() { + return { navigation, railEntries, loadNavigation, firstControllerPath, activeEntry, homePath } +} diff --git a/admin/src/styles/main.css b/admin/src/styles/main.css new file mode 100644 index 0000000..312a4d5 --- /dev/null +++ b/admin/src/styles/main.css @@ -0,0 +1,160 @@ +@import "tailwindcss"; + +/* Self-hosted fonts (D-07). Whole-weight files carry latin and latin-ext with + unicode-range, so Polish diacritics and basic Latin both resolve. */ +@import "@fontsource/dm-sans/400.css"; +@import "@fontsource/dm-sans/500.css"; +@import "@fontsource/dm-sans/600.css"; +@import "@fontsource/dm-sans/700.css"; +@import "@fontsource/dm-mono/400.css"; +@import "@fontsource/dm-mono/500.css"; + +@custom-variant dark (&:where(.dark, .dark *)); + +/* Design tokens from design/README.md (Direction C v2). Utilities read CSS + variables so light and dark mode swap values without new classes. */ +@theme { + --font-sans: "DM Sans", ui-sans-serif, system-ui, sans-serif; + --font-mono: "DM Mono", ui-monospace, monospace; + + --color-bg: var(--c-bg); + --color-surface: var(--c-surface); + --color-subtle: var(--c-subtle); + --color-border: var(--c-border); + --color-border-strong: var(--c-border-strong); + --color-text: var(--c-text); + --color-muted: var(--c-muted); + --color-placeholder: var(--c-placeholder); + --color-primary: var(--c-primary); + --color-on-primary: var(--c-on-primary); + --color-danger: var(--c-danger); + --color-danger-soft: var(--c-danger-soft); + --color-ok-bg: var(--c-ok-bg); + --color-ok-text: var(--c-ok-text); + --color-ring: var(--c-ring); + --color-hover: var(--c-hover); + --color-sel: var(--c-sel); + --color-skel: var(--c-skel); + --color-overlay: var(--c-overlay); + --color-side: var(--c-side); + --color-side-border: var(--c-side-border); + --color-accent: #fcd34d; + --color-on-accent: #1b2540; + --color-accent-soft: rgba(252, 211, 77, 0.14); + --color-side-text: #c3cbda; + --color-side-label: #9aa6bd; + --color-side-hover: rgba(255, 255, 255, 0.08); + + --radius-control: 10px; + --radius-card: 16px; + --radius-modal: 20px; + --radius-inner: 12px; + --radius-tab: 9px; + --radius-checkbox: 5px; + --radius-pager: 8px; + --radius-pill: 999px; + + --spacing-button: 42px; + --spacing-input: 44px; + --spacing-header: 64px; + --spacing-nav: 40px; + --spacing-row: 54px; + --spacing-row-head: 44px; + --spacing-pager: 34px; + --spacing-rail: 80px; + --spacing-panel: 224px; + + --shadow-card: var(--c-shadow-card); + --shadow-login: 0 24px 60px rgba(0, 0, 0, 0.35); + --shadow-pop: 0 16px 40px rgba(20, 27, 45, 0.18); + --shadow-menu: 0 16px 40px rgba(20, 27, 45, 0.16); + --shadow-toast: 0 16px 40px rgba(0, 0, 0, 0.25); + --shadow-tab: 0 1px 3px rgba(20, 27, 45, 0.12); +} + +:root { + --c-bg: #f4f6f9; + --c-surface: #ffffff; + --c-subtle: #f3f5f8; + --c-border: #e6e9ef; + --c-border-strong: #d2d8e2; + --c-text: #141b2d; + --c-muted: #566175; + --c-placeholder: #6b7588; + --c-primary: #22304d; + --c-on-primary: #ffffff; + --c-danger: #c62828; + --c-danger-soft: #fdf0f0; + --c-ok-bg: #e3f4e8; + --c-ok-text: #1c6b35; + --c-ring: rgba(252, 196, 40, 0.55); + --c-hover: #f1f3f7; + --c-sel: #fdf3cf; + --c-skel: #eceff4; + --c-overlay: rgba(20, 27, 45, 0.5); + --c-side: #1d2740; + --c-side-border: transparent; + --c-shadow-card: 0 1px 2px rgba(20, 27, 45, 0.04), 0 4px 16px rgba(20, 27, 45, 0.05); +} + +.dark { + --c-bg: #111726; + --c-surface: #182033; + --c-subtle: #1f283d; + --c-border: #29334b; + --c-border-strong: #3a4661; + --c-text: #eef1f6; + --c-muted: #a9b3c6; + --c-placeholder: #8a95ab; + --c-primary: #fcd34d; + --c-on-primary: #1b2540; + --c-danger: #f58a8a; + --c-danger-soft: #3a1d24; + --c-ok-bg: #173826; + --c-ok-text: #8fdfa8; + --c-ring: rgba(252, 211, 77, 0.45); + --c-hover: #212b42; + --c-sel: #3a3622; + --c-skel: #263049; + --c-overlay: rgba(5, 8, 16, 0.7); + --c-side: #0d1320; + --c-side-border: #222b40; + --c-shadow-card: none; +} + +@layer base { + html { + font-family: var(--font-sans); + font-size: 14px; + line-height: 1.5; + color: var(--c-text); + background: var(--c-bg); + } + + body { + margin: 0; + min-height: 100vh; + } + + /* Every interactive element shows the 3px ring; never remove it. */ + a:focus-visible, + button:focus-visible, + [role="button"]:focus-visible, + [tabindex]:focus-visible { + outline: 3px solid var(--c-ring); + outline-offset: 2px; + } + + input:focus-visible, + select:focus-visible, + textarea:focus-visible { + outline: none; + border-color: var(--c-primary); + box-shadow: 0 0 0 3px var(--c-ring); + } + + input::placeholder, + textarea::placeholder { + color: var(--c-placeholder); + } +} diff --git a/admin/src/views/ListView.vue b/admin/src/views/ListView.vue new file mode 100644 index 0000000..5587a4f --- /dev/null +++ b/admin/src/views/ListView.vue @@ -0,0 +1,89 @@ + + + diff --git a/admin/src/views/LoginView.vue b/admin/src/views/LoginView.vue new file mode 100644 index 0000000..393d9b8 --- /dev/null +++ b/admin/src/views/LoginView.vue @@ -0,0 +1,99 @@ + + + diff --git a/admin/src/views/NotFoundView.vue b/admin/src/views/NotFoundView.vue new file mode 100644 index 0000000..43cce99 --- /dev/null +++ b/admin/src/views/NotFoundView.vue @@ -0,0 +1,13 @@ + + + diff --git a/admin/tests/fixtures/navigation.json b/admin/tests/fixtures/navigation.json new file mode 100644 index 0000000..19e5f19 --- /dev/null +++ b/admin/tests/fixtures/navigation.json @@ -0,0 +1,55 @@ +{ + "data": [ + { + "code": "demo", + "label": "Demo", + "icon": "disc-3", + "order": 100, + "controller": "acme.demo.widgets", + "sideMenu": [ + { + "code": "widgets", + "label": "Widgets", + "icon": "tags", + "order": 0, + "controller": "acme.demo.widgets", + "sideMenu": [] + }, + { + "code": "gadgets", + "label": "Gadgets", + "icon": "icon-archive", + "order": 0, + "controller": "acme.demo.gadgets", + "sideMenu": [] + } + ] + }, + { + "code": "tools", + "label": "Tools", + "icon": "unknown-icon-name", + "order": 300, + "controller": "acme.tools.hammers", + "sideMenu": [ + { + "code": "hammers", + "label": "Hammers", + "icon": "puzzle", + "order": 0, + "controller": "acme.tools.hammers", + "sideMenu": [] + } + ] + }, + { + "code": "hidden", + "label": "Hidden", + "icon": "users", + "order": 200, + "controller": "acme.hidden.items", + "sideMenu": [] + } + ], + "meta": { "locale": "en" } +} diff --git a/admin/tests/fixtures/widgets.list-schema.json b/admin/tests/fixtures/widgets.list-schema.json new file mode 100644 index 0000000..91743bb --- /dev/null +++ b/admin/tests/fixtures/widgets.list-schema.json @@ -0,0 +1,22 @@ +{ + "data": { + "title": "Widgets", + "recordsPerPage": 20, + "perPageOptions": [], + "showSearch": true, + "showSetup": false, + "showCheckboxes": true, + "showSorting": true, + "searchTerm": "", + "toolbarButtons": ["create"], + "columns": [ + { "key": "name", "label": "Name", "searchable": true, "sortable": true }, + { "key": "code", "label": "Code", "searchable": true, "sortable": true }, + { "key": "tags", "label": "Tags", "searchable": false, "sortable": false, "type": "relation", "relation": "tags", "select": "name" } + ], + "filters": [], + "rowActions": [], + "bulkActions": [] + }, + "meta": { "locale": "en" } +} diff --git a/admin/tests/fixtures/widgets.list.json b/admin/tests/fixtures/widgets.list.json new file mode 100644 index 0000000..e4f74da --- /dev/null +++ b/admin/tests/fixtures/widgets.list.json @@ -0,0 +1,7 @@ +{ + "data": [ + { "id": 1, "name": "Blue widget", "code": "W-01", "tags": ["small", "round"] }, + { "id": 2, "name": "Green widget", "code": "W-02", "tags": [] } + ], + "meta": { "page": 1, "per_page": 20, "total": 2, "last_page": 1 } +} diff --git a/admin/tests/setup.ts b/admin/tests/setup.ts new file mode 100644 index 0000000..ed9a30e --- /dev/null +++ b/admin/tests/setup.ts @@ -0,0 +1,6 @@ +// The served index.html carries the admin base in a meta element; tests use a +// non-default base to prove the SPA reads it at runtime (D-02, D-03). +const meta = document.createElement('meta') +meta.setAttribute('name', 'summer-admin-base') +meta.setAttribute('content', '/admin-test') +document.head.appendChild(meta) diff --git a/admin/tests/smoke/tracer.smoke.test.ts b/admin/tests/smoke/tracer.smoke.test.ts new file mode 100644 index 0000000..95916fb --- /dev/null +++ b/admin/tests/smoke/tracer.smoke.test.ts @@ -0,0 +1,199 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { flushPromises, mount } from '@vue/test-utils' +import { createMemoryHistory } from 'vue-router' +import App from '../../src/App.vue' +import LoginView from '../../src/views/LoginView.vue' +import { createAdminRouter, safeRedirect } from '../../src/app/router' +import { runtime } from '../../src/app/runtime' +import { onUnauthorized } from '../../src/api/client' +import { clearUser, me, useAuth } from '../../src/state/useAuth' +import { loadNavigation, setNavigation } from '../../src/state/useNavigation' +import navigation from '../fixtures/navigation.json' +import listSchema from '../fixtures/widgets.list-schema.json' +import listRows from '../fixtures/widgets.list.json' + +const API = '/admin-test/api/v1' + +const profile = { + data: { + id: 7, + login: 'dev', + email: 'dev@example.test', + first_name: 'Dana', + last_name: 'Dev', + is_superuser: false, + role: { id: 2, code: 'developer', name: 'Developer' }, + }, + meta: {}, +} + +type Routes = Record + +function mockApi(routes: Routes): Request[] { + const calls: Request[] = [] + vi.spyOn(globalThis, 'fetch').mockImplementation(async (input: RequestInfo | URL) => { + const request = input as Request + calls.push(request) + const key = `${request.method} ${new URL(request.url).pathname}` + const route = routes[key] + const status = route ? (route.status ?? 200) : 404 + const body = route ? route.body : { error: { code: 'not_found', message: 'Not found', details: {} } } + return new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json' } }) + }) + return calls +} + +function pathOf(request: Request): string { + return new URL(request.url).pathname +} + +beforeEach(() => { + clearUser() + setNavigation([]) + onUnauthorized(null) +}) + +afterEach(() => { + localStorage.clear() + sessionStorage.clear() +}) + +describe('runtime', () => { + it('reads the admin base and API base from the served meta', () => { + expect(runtime.base).toBe('/admin-test') + expect(runtime.api).toBe(API) + }) +}) + +describe('login', () => { + it('posts the CSRF header with same-origin credentials and stores no token', async () => { + const calls = mockApi({ + [`POST ${API}/auth/login`]: { + body: { data: { token_type: 'cookie', expires_in: 3600, access_token: 'must-not-be-kept' }, meta: {} }, + }, + }) + const auth = useAuth() + + expect(await auth.login('dev', 'secret')).toBe(true) + + expect(calls).toHaveLength(1) + const request = calls[0]! + expect(request.method).toBe('POST') + expect(pathOf(request)).toBe(`${API}/auth/login`) + expect(request.headers.get('X-Requested-With')).toBe('XMLHttpRequest') + expect(request.credentials).toBe('same-origin') + expect(await request.json()).toEqual({ login: 'dev', password: 'secret' }) + expect(auth.expiresIn.value).toBe(3600) + expect(localStorage.length).toBe(0) + expect(sessionStorage.length).toBe(0) + expect(document.cookie).not.toContain('must-not-be-kept') + expect(JSON.stringify({ user: auth.user.value, expiresIn: auth.expiresIn.value })).not.toContain('must-not-be-kept') + }) + + it('shows the invalid-credentials alert and marks the password invalid', async () => { + mockApi({ + [`POST ${API}/auth/login`]: { + status: 401, + body: { error: { code: 'unauthenticated', message: 'Invalid credentials', details: {} } }, + }, + }) + const router = createAdminRouter(createMemoryHistory()) + await router.push('/login') + const wrapper = mount(LoginView, { global: { plugins: [router] } }) + await wrapper.find('input[name="login"]').setValue('dev') + await wrapper.find('input[name="password"]').setValue('wrong') + await wrapper.find('form').trigger('submit') + await flushPromises() + + expect(wrapper.find('[role="alert"]').exists()).toBe(true) + expect(wrapper.find('input[name="password"]').attributes('aria-invalid')).toBe('true') + expect(router.currentRoute.value.name).toBe('login') + }) +}) + +describe('redirects', () => { + it('accepts only in-app paths that start with exactly one slash', () => { + expect(safeRedirect('/acme/demo/widgets?page=2')).toBe('/acme/demo/widgets?page=2') + expect(safeRedirect('https://evil.example/steal')).toBeNull() + expect(safeRedirect('//evil.example/steal')).toBeNull() + expect(safeRedirect('/\\evil.example')).toBeNull() + expect(safeRedirect('acme/demo')).toBeNull() + expect(safeRedirect(['/acme'])).toBeNull() + }) + + it('sends an unauthenticated visitor to login with the requested path', async () => { + const router = createAdminRouter(createMemoryHistory()) + await router.push('/acme/demo/widgets?page=2') + expect(router.currentRoute.value.name).toBe('login') + expect(router.currentRoute.value.query.redirect).toBe('/acme/demo/widgets?page=2') + }) + + it('reports a 401 on any call other than login', async () => { + mockApi({ + [`GET ${API}/auth/me`]: { status: 401, body: { error: { code: 'unauthenticated', message: 'Unauthenticated', details: {} } } }, + [`POST ${API}/auth/login`]: { status: 401, body: { error: { code: 'unauthenticated', message: 'Invalid credentials', details: {} } } }, + }) + const handler = vi.fn() + onUnauthorized(handler) + await useAuth().login('dev', 'wrong') + expect(handler).not.toHaveBeenCalled() + await me() + expect(handler).toHaveBeenCalledTimes(1) + }) +}) + +describe('navigation shell and list', () => { + async function mountApp() { + const calls = mockApi({ + [`GET ${API}/auth/me`]: { body: profile }, + [`GET ${API}/navigation`]: { body: navigation }, + [`GET ${API}/acme/demo/widgets/schema/list`]: { body: listSchema }, + [`GET ${API}/acme/demo/widgets`]: { body: listRows }, + }) + await me() + await loadNavigation() + const router = createAdminRouter(createMemoryHistory()) + await router.push('/acme/demo/widgets') + const wrapper = mount(App, { global: { plugins: [router] } }) + await flushPromises() + return { wrapper, calls, router } + } + + it('renders navigation grouped by plugin and omits a plugin with an empty side menu', async () => { + const { wrapper } = await mountApp() + const rail = wrapper.find('nav[aria-label="backend::lang.nav.plugins"]') + expect(rail.exists()).toBe(true) + const railLinks = rail.findAll('a') + expect(railLinks.map((link) => link.text())).toEqual(['Demo', 'Tools']) + expect(railLinks[0]!.attributes('aria-current')).toBe('page') + expect(railLinks[1]!.attributes('aria-current')).toBeUndefined() + expect(railLinks[1]!.attributes('href')).toBe('/acme/tools/hammers') + // An unknown icon name renders the neutral fallback instead of failing. + expect(railLinks[1]!.find('svg').exists()).toBe(true) + + const panel = wrapper.find('aside') + const panelLinks = panel.findAll('a') + expect(panel.text()).toContain('Demo') + expect(panelLinks.map((link) => link.text())).toEqual(['Widgets', 'Gadgets']) + expect(panelLinks[0]!.attributes('aria-current')).toBe('page') + expect(panelLinks[1]!.attributes('href')).toBe('/acme/demo/gadgets') + }) + + it('renders the list schema columns and rows through the typed client', async () => { + const { wrapper, calls } = await mountApp() + const requested = calls.map(pathOf) + expect(requested).toContain(`${API}/acme/demo/widgets/schema/list`) + expect(requested).toContain(`${API}/acme/demo/widgets`) + for (const request of calls) { + expect(request.headers.get('X-Requested-With')).toBe('XMLHttpRequest') + } + + expect(wrapper.find('h1').text()).toBe('Widgets') + expect(wrapper.findAll('th').map((th) => th.text())).toEqual(['Name', 'Code', 'Tags']) + const rows = wrapper.findAll('tbody tr').map((tr) => tr.findAll('td').map((td) => td.text())) + expect(rows).toEqual([ + ['Blue widget', 'W-01', 'small, round'], + ['Green widget', 'W-02', '—'], + ]) + }) +}) diff --git a/admin/tsconfig.json b/admin/tsconfig.json new file mode 100644 index 0000000..8dbaf9a --- /dev/null +++ b/admin/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "verbatimModuleSyntax": true, + "isolatedModules": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "noEmit": true, + "jsx": "preserve", + "types": [] + }, + "include": ["src/**/*.ts", "src/**/*.vue", "tests/**/*.ts", "env.d.ts"] +} diff --git a/admin/vite.config.ts b/admin/vite.config.ts new file mode 100644 index 0000000..d7670df --- /dev/null +++ b/admin/vite.config.ts @@ -0,0 +1,34 @@ +import { defineConfig, type Plugin } from 'vite' +import vue from '@vitejs/plugin-vue' +import tailwindcss from '@tailwindcss/vite' + +// The committed build is path-agnostic (D-02): assets use a relative base and +// index.html keeps the __SUMMER_ADMIN_BASE__ token, which the Go server +// (boardwalk) replaces with the configured backend.uri. The dev server +// replaces it with SUMMER_ADMIN_DEV_PREFIX and proxies the admin API to a +// running `summer serve` at SUMMER_ADMIN_DEV_TARGET. +const devPrefix = (process.env.SUMMER_ADMIN_DEV_PREFIX ?? '/backend').replace(/\/+$/, '') +const devTarget = process.env.SUMMER_ADMIN_DEV_TARGET ?? 'http://localhost:8080' + +function devBase(): Plugin { + return { + name: 'summer-admin-dev-base', + apply: 'serve', + transformIndexHtml: (html) => html.replace('__SUMMER_ADMIN_BASE__', devPrefix), + } +} + +export default defineConfig(({ command }) => ({ + base: command === 'build' ? './' : '/', + plugins: [vue(), tailwindcss(), devBase()], + build: { + outDir: '../boardwalk/dist', + emptyOutDir: true, + sourcemap: false, + }, + server: { + proxy: { + [`${devPrefix}/api`]: { target: devTarget, changeOrigin: false }, + }, + }, +})) diff --git a/admin/vitest.config.ts b/admin/vitest.config.ts new file mode 100644 index 0000000..8a2d0a2 --- /dev/null +++ b/admin/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], + test: { + environment: 'happy-dom', + include: ['tests/**/*.test.ts'], + setupFiles: ['tests/setup.ts'], + restoreMocks: true, + }, +}) diff --git a/boardwalk/boardwalk.go b/boardwalk/boardwalk.go new file mode 100644 index 0000000..aacf193 --- /dev/null +++ b/boardwalk/boardwalk.go @@ -0,0 +1,167 @@ +// Package boardwalk serves the embedded admin SPA build (D-01, D-02). +// +// The committed dist/ is path-agnostic: Vite builds it with a relative base +// and index.html carries the __SUMMER_ADMIN_BASE__ token. Handler rewrites +// index.html once for the configured backend.uri, serves hashed assets with +// long-lived caching, falls back to index.html for client-side routes and +// hands every unmatched api/ path back to the caller so API misses stay JSON. +package boardwalk + +import ( + "bytes" + "embed" + "errors" + "fmt" + "html" + "io/fs" + "mime" + "net/http" + "path" + "strings" + "time" +) + +//go:embed all:dist +var distFS embed.FS + +// BaseToken is replaced in dist/index.html by the configured admin prefix. +const BaseToken = "__SUMMER_ADMIN_BASE__" + +// contentSecurityPolicy keeps the admin out of frames and allows scripts +// only from its own origin; the build contains no inline script. +const contentSecurityPolicy = "frame-ancestors 'none'; base-uri 'none'; object-src 'none'; script-src 'self'" + +var contentTypes = map[string]string{ + ".js": "text/javascript; charset=utf-8", + ".mjs": "text/javascript; charset=utf-8", + ".css": "text/css; charset=utf-8", + ".html": "text/html; charset=utf-8", + ".woff2": "font/woff2", + ".woff": "font/woff", + ".svg": "image/svg+xml", + ".json": "application/json", +} + +// Dist returns the embedded build tree rooted at dist/. +func Dist() (fs.FS, error) { + return fs.Sub(distFS, "dist") +} + +// Handler serves the embedded SPA under prefix (for example /backend). +// notFoundAPI answers any request whose path under the prefix is api or +// starts with api/; it must write the admin API's JSON 404 envelope. +func Handler(prefix string, notFoundAPI http.Handler) (http.Handler, error) { + root, err := Dist() + if err != nil { + return nil, err + } + return newHandler(root, prefix, notFoundAPI) +} + +func newHandler(root fs.FS, prefix string, notFoundAPI http.Handler) (http.Handler, error) { + if notFoundAPI == nil { + return nil, errors.New("boardwalk: notFoundAPI handler is nil") + } + prefix = strings.TrimRight(prefix, "/") + if !strings.HasPrefix(prefix, "/") { + return nil, fmt.Errorf("boardwalk: prefix %q must start with /", prefix) + } + raw, err := fs.ReadFile(root, "index.html") + if err != nil { + return nil, fmt.Errorf("boardwalk: dist/index.html: %w", err) + } + index, err := RewriteIndex(raw, prefix) + if err != nil { + return nil, err + } + return &handler{root: root, prefix: prefix, index: index, notFoundAPI: notFoundAPI}, nil +} + +// RewriteIndex points relative asset URLs at prefix and injects the prefix +// into the summer-admin-base meta. It fails when the token is absent, which +// catches a stale or hand-edited dist at boot. +func RewriteIndex(raw []byte, prefix string) ([]byte, error) { + if !bytes.Contains(raw, []byte(BaseToken)) { + return nil, errors.New("boardwalk: dist/index.html has no " + BaseToken + " token; rebuild the admin SPA") + } + escaped := html.EscapeString(prefix) + out := bytes.ReplaceAll(raw, []byte(`="./`), []byte(`="`+escaped+`/`)) + out = bytes.ReplaceAll(out, []byte(BaseToken), []byte(escaped)) + return out, nil +} + +type handler struct { + root fs.FS + prefix string + index []byte + notFoundAPI http.Handler +} + +func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + setSecurityHeaders(w.Header()) + rel := strings.TrimPrefix(r.URL.Path, h.prefix) + rel = strings.TrimPrefix(rel, "/") + if rel == "api" || strings.HasPrefix(rel, "api/") { + h.notFoundAPI.ServeHTTP(w, r) + return + } + name := strings.TrimPrefix(path.Clean("/"+rel), "/") + if name == "" || name == "index.html" { + h.serveIndex(w, r) + return + } + if info, err := fs.Stat(h.root, name); err == nil { + if info.Mode().IsRegular() { + h.serveFile(w, r, name) + return + } + // A directory is never listed; it is treated as a client route. + h.serveIndex(w, r) + return + } + if path.Ext(name) != "" { + http.NotFound(w, r) + return + } + h.serveIndex(w, r) +} + +func (h *handler) serveIndex(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Header().Set("Cache-Control", "no-store") + http.ServeContent(w, r, "index.html", time.Time{}, bytes.NewReader(h.index)) +} + +func (h *handler) serveFile(w http.ResponseWriter, r *http.Request, name string) { + body, err := fs.ReadFile(h.root, name) + if err != nil { + http.NotFound(w, r) + return + } + w.Header().Set("Content-Type", contentType(name)) + if strings.HasPrefix(name, "assets/") { + w.Header().Set("Cache-Control", "public, max-age=31536000, immutable") + } else { + w.Header().Set("Cache-Control", "no-cache") + } + http.ServeContent(w, r, path.Base(name), time.Time{}, bytes.NewReader(body)) +} + +func contentType(name string) string { + ext := strings.ToLower(path.Ext(name)) + if ct, ok := contentTypes[ext]; ok { + return ct + } + if ct := mime.TypeByExtension(ext); ct != "" { + return ct + } + return "application/octet-stream" +} + +func setSecurityHeaders(h http.Header) { + h.Set("X-Content-Type-Options", "nosniff") + h.Set("Referrer-Policy", "same-origin") + h.Set("X-Frame-Options", "DENY") + h.Set("Content-Security-Policy", contentSecurityPolicy) + h.Set("X-Robots-Tag", "noindex, nofollow") +} diff --git a/boardwalk/dist/assets/dm-mono-latin-400-normal--0xN8mdc.woff b/boardwalk/dist/assets/dm-mono-latin-400-normal--0xN8mdc.woff new file mode 100644 index 0000000..b04bf00 Binary files /dev/null and b/boardwalk/dist/assets/dm-mono-latin-400-normal--0xN8mdc.woff differ diff --git a/boardwalk/dist/assets/dm-mono-latin-400-normal-4GdczIuU.woff2 b/boardwalk/dist/assets/dm-mono-latin-400-normal-4GdczIuU.woff2 new file mode 100644 index 0000000..03e4859 Binary files /dev/null and b/boardwalk/dist/assets/dm-mono-latin-400-normal-4GdczIuU.woff2 differ diff --git a/boardwalk/dist/assets/dm-mono-latin-500-normal-CN8Miw6E.woff b/boardwalk/dist/assets/dm-mono-latin-500-normal-CN8Miw6E.woff new file mode 100644 index 0000000..08f5d19 Binary files /dev/null and b/boardwalk/dist/assets/dm-mono-latin-500-normal-CN8Miw6E.woff differ diff --git a/boardwalk/dist/assets/dm-mono-latin-500-normal-DRMDZjhP.woff2 b/boardwalk/dist/assets/dm-mono-latin-500-normal-DRMDZjhP.woff2 new file mode 100644 index 0000000..67698d8 Binary files /dev/null and b/boardwalk/dist/assets/dm-mono-latin-500-normal-DRMDZjhP.woff2 differ diff --git a/boardwalk/dist/assets/dm-mono-latin-ext-400-normal-1aZr6b2b.woff b/boardwalk/dist/assets/dm-mono-latin-ext-400-normal-1aZr6b2b.woff new file mode 100644 index 0000000..fc53f73 Binary files /dev/null and b/boardwalk/dist/assets/dm-mono-latin-ext-400-normal-1aZr6b2b.woff differ diff --git a/boardwalk/dist/assets/dm-mono-latin-ext-400-normal-C2zvOubV.woff2 b/boardwalk/dist/assets/dm-mono-latin-ext-400-normal-C2zvOubV.woff2 new file mode 100644 index 0000000..9785e91 Binary files /dev/null and b/boardwalk/dist/assets/dm-mono-latin-ext-400-normal-C2zvOubV.woff2 differ diff --git a/boardwalk/dist/assets/dm-mono-latin-ext-500-normal-BtRyHRi6.woff2 b/boardwalk/dist/assets/dm-mono-latin-ext-500-normal-BtRyHRi6.woff2 new file mode 100644 index 0000000..b872009 Binary files /dev/null and b/boardwalk/dist/assets/dm-mono-latin-ext-500-normal-BtRyHRi6.woff2 differ diff --git a/boardwalk/dist/assets/dm-mono-latin-ext-500-normal-Dw3M13d8.woff b/boardwalk/dist/assets/dm-mono-latin-ext-500-normal-Dw3M13d8.woff new file mode 100644 index 0000000..9490ad7 Binary files /dev/null and b/boardwalk/dist/assets/dm-mono-latin-ext-500-normal-Dw3M13d8.woff differ diff --git a/boardwalk/dist/assets/dm-sans-latin-400-normal-BwCSEQnW.woff b/boardwalk/dist/assets/dm-sans-latin-400-normal-BwCSEQnW.woff new file mode 100644 index 0000000..e9a3b30 Binary files /dev/null and b/boardwalk/dist/assets/dm-sans-latin-400-normal-BwCSEQnW.woff differ diff --git a/boardwalk/dist/assets/dm-sans-latin-400-normal-CW0RaeGs.woff2 b/boardwalk/dist/assets/dm-sans-latin-400-normal-CW0RaeGs.woff2 new file mode 100644 index 0000000..0b8bc55 Binary files /dev/null and b/boardwalk/dist/assets/dm-sans-latin-400-normal-CW0RaeGs.woff2 differ diff --git a/boardwalk/dist/assets/dm-sans-latin-500-normal-B9HHJjqV.woff2 b/boardwalk/dist/assets/dm-sans-latin-500-normal-B9HHJjqV.woff2 new file mode 100644 index 0000000..48e1612 Binary files /dev/null and b/boardwalk/dist/assets/dm-sans-latin-500-normal-B9HHJjqV.woff2 differ diff --git a/boardwalk/dist/assets/dm-sans-latin-500-normal-Dr3UlScf.woff b/boardwalk/dist/assets/dm-sans-latin-500-normal-Dr3UlScf.woff new file mode 100644 index 0000000..d34469f Binary files /dev/null and b/boardwalk/dist/assets/dm-sans-latin-500-normal-Dr3UlScf.woff differ diff --git a/boardwalk/dist/assets/dm-sans-latin-600-normal-Aqo67rzb.woff2 b/boardwalk/dist/assets/dm-sans-latin-600-normal-Aqo67rzb.woff2 new file mode 100644 index 0000000..5e39dbc Binary files /dev/null and b/boardwalk/dist/assets/dm-sans-latin-600-normal-Aqo67rzb.woff2 differ diff --git a/boardwalk/dist/assets/dm-sans-latin-600-normal-BmdmIIQ2.woff b/boardwalk/dist/assets/dm-sans-latin-600-normal-BmdmIIQ2.woff new file mode 100644 index 0000000..4a8bb66 Binary files /dev/null and b/boardwalk/dist/assets/dm-sans-latin-600-normal-BmdmIIQ2.woff differ diff --git a/boardwalk/dist/assets/dm-sans-latin-700-normal-CUSSCpQX.woff b/boardwalk/dist/assets/dm-sans-latin-700-normal-CUSSCpQX.woff new file mode 100644 index 0000000..5c43e64 Binary files /dev/null and b/boardwalk/dist/assets/dm-sans-latin-700-normal-CUSSCpQX.woff differ diff --git a/boardwalk/dist/assets/dm-sans-latin-700-normal-DvUfVpUG.woff2 b/boardwalk/dist/assets/dm-sans-latin-700-normal-DvUfVpUG.woff2 new file mode 100644 index 0000000..26edc56 Binary files /dev/null and b/boardwalk/dist/assets/dm-sans-latin-700-normal-DvUfVpUG.woff2 differ diff --git a/boardwalk/dist/assets/dm-sans-latin-ext-400-normal-BjWJ59Pq.woff b/boardwalk/dist/assets/dm-sans-latin-ext-400-normal-BjWJ59Pq.woff new file mode 100644 index 0000000..0113bc2 Binary files /dev/null and b/boardwalk/dist/assets/dm-sans-latin-ext-400-normal-BjWJ59Pq.woff differ diff --git a/boardwalk/dist/assets/dm-sans-latin-ext-400-normal-BtiwyxMk.woff2 b/boardwalk/dist/assets/dm-sans-latin-ext-400-normal-BtiwyxMk.woff2 new file mode 100644 index 0000000..6d20410 Binary files /dev/null and b/boardwalk/dist/assets/dm-sans-latin-ext-400-normal-BtiwyxMk.woff2 differ diff --git a/boardwalk/dist/assets/dm-sans-latin-ext-500-normal-BJfUCQsA.woff2 b/boardwalk/dist/assets/dm-sans-latin-ext-500-normal-BJfUCQsA.woff2 new file mode 100644 index 0000000..ab2af3e Binary files /dev/null and b/boardwalk/dist/assets/dm-sans-latin-ext-500-normal-BJfUCQsA.woff2 differ diff --git a/boardwalk/dist/assets/dm-sans-latin-ext-500-normal-DR84L5F-.woff b/boardwalk/dist/assets/dm-sans-latin-ext-500-normal-DR84L5F-.woff new file mode 100644 index 0000000..d1f80c6 Binary files /dev/null and b/boardwalk/dist/assets/dm-sans-latin-ext-500-normal-DR84L5F-.woff differ diff --git a/boardwalk/dist/assets/dm-sans-latin-ext-600-normal-4vooXBpG.woff2 b/boardwalk/dist/assets/dm-sans-latin-ext-600-normal-4vooXBpG.woff2 new file mode 100644 index 0000000..16ca393 Binary files /dev/null and b/boardwalk/dist/assets/dm-sans-latin-ext-600-normal-4vooXBpG.woff2 differ diff --git a/boardwalk/dist/assets/dm-sans-latin-ext-600-normal-DRtaDpgU.woff b/boardwalk/dist/assets/dm-sans-latin-ext-600-normal-DRtaDpgU.woff new file mode 100644 index 0000000..46e92f7 Binary files /dev/null and b/boardwalk/dist/assets/dm-sans-latin-ext-600-normal-DRtaDpgU.woff differ diff --git a/boardwalk/dist/assets/dm-sans-latin-ext-700-normal-BLI3TTWz.woff b/boardwalk/dist/assets/dm-sans-latin-ext-700-normal-BLI3TTWz.woff new file mode 100644 index 0000000..568d0ca Binary files /dev/null and b/boardwalk/dist/assets/dm-sans-latin-ext-700-normal-BLI3TTWz.woff differ diff --git a/boardwalk/dist/assets/dm-sans-latin-ext-700-normal-CJIcxD6K.woff2 b/boardwalk/dist/assets/dm-sans-latin-ext-700-normal-CJIcxD6K.woff2 new file mode 100644 index 0000000..78d90f3 Binary files /dev/null and b/boardwalk/dist/assets/dm-sans-latin-ext-700-normal-CJIcxD6K.woff2 differ diff --git a/boardwalk/dist/assets/index-UTAit0wB.css b/boardwalk/dist/assets/index-UTAit0wB.css new file mode 100644 index 0000000..6edd8fe --- /dev/null +++ b/boardwalk/dist/assets/index-UTAit0wB.css @@ -0,0 +1 @@ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--font-sans:"DM Sans", ui-sans-serif, system-ui, sans-serif;--font-mono:"DM Mono", ui-monospace, monospace;--color-white:#fff;--spacing:.25rem;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--leading-tight:1.25;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-bg:var(--c-bg);--color-surface:var(--c-surface);--color-subtle:var(--c-subtle);--color-border:var(--c-border);--color-text:var(--c-text);--color-muted:var(--c-muted);--color-danger:var(--c-danger);--color-hover:var(--c-hover);--color-sel:var(--c-sel);--color-skel:var(--c-skel);--color-side:var(--c-side);--color-side-border:var(--c-side-border);--color-accent:#fcd34d;--color-on-accent:#1b2540;--color-accent-soft:#fcd34d24;--color-side-text:#c3cbda;--color-side-hover:#ffffff14;--radius-control:10px;--radius-card:16px;--radius-inner:12px;--spacing-input:44px;--spacing-header:64px;--spacing-nav:40px;--spacing-row:54px;--spacing-row-head:44px;--spacing-rail:80px;--spacing-panel:224px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}html{font-family:var(--font-sans);color:var(--c-text);background:var(--c-bg);font-size:14px;line-height:1.5}body{min-height:100vh;margin:0}a:focus-visible,button:focus-visible,[role=button]:focus-visible,[tabindex]:focus-visible{outline:3px solid var(--c-ring);outline-offset:2px}input:focus-visible,select:focus-visible,textarea:focus-visible{border-color:var(--c-primary);box-shadow:0 0 0 3px var(--c-ring);outline:none}input::placeholder,textarea::placeholder{color:var(--c-placeholder)}}@layer components;@layer utilities{.absolute{position:absolute}.relative{position:relative}.mb-3\.5{margin-bottom:calc(var(--spacing) * 3.5)}.block{display:block}.flex{display:flex}.hidden{display:none}.table{display:table}.size-10{width:calc(var(--spacing) * 10);height:calc(var(--spacing) * 10)}.size-\[34px\]{width:34px;height:34px}.h-3{height:calc(var(--spacing) * 3)}.h-10{height:calc(var(--spacing) * 10)}.h-header{height:var(--spacing-header)}.h-input{height:var(--spacing-input)}.h-nav{height:var(--spacing-nav)}.h-row{height:var(--spacing-row)}.h-row-head{height:var(--spacing-row-head)}.min-h-screen{min-height:100vh}.w-3\/5{width:60%}.w-\[68px\]{width:68px}.w-full{width:100%}.w-panel{width:var(--spacing-panel)}.w-rail{width:var(--spacing-rail)}.max-w-\[400px\]{max-width:400px}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[640px\]{min-width:640px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-\[18px\]{gap:18px}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded-\[6px\]{border-radius:6px}.rounded-card{border-radius:var(--radius-card)}.rounded-control{border-radius:var(--radius-control)}.rounded-full{border-radius:3.40282e38px}.rounded-inner{border-radius:var(--radius-inner)}.border{border-style:var(--tw-border-style);border-width:1px}.border-y{border-block-style:var(--tw-border-style);border-block-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-\[\#c62828\]{border-color:#c62828}.border-\[\#d2d8e2\]{border-color:#d2d8e2}.border-border{border-color:var(--color-border)}.border-side-border{border-color:var(--color-side-border)}.bg-\[\#22304d\]{background-color:#22304d}.bg-\[\#fdf0f0\]{background-color:#fdf0f0}.bg-accent{background-color:var(--color-accent)}.bg-accent-soft{background-color:var(--color-accent-soft)}.bg-bg{background-color:var(--color-bg)}.bg-sel{background-color:var(--color-sel)}.bg-side{background-color:var(--color-side)}.bg-skel{background-color:var(--color-skel)}.bg-subtle{background-color:var(--color-subtle)}.bg-surface{background-color:var(--color-surface)}.bg-white{background-color:var(--color-white)}.p-8{padding:calc(var(--spacing) * 8)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-8{padding-inline:calc(var(--spacing) * 8)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-3\.5{padding-block:calc(var(--spacing) * 3.5)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-7{padding-block:calc(var(--spacing) * 7)}.py-20{padding-block:calc(var(--spacing) * 20)}.pt-3\.5{padding-top:calc(var(--spacing) * 3.5)}.pr-6{padding-right:calc(var(--spacing) * 6)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pl-8{padding-left:calc(var(--spacing) * 8)}.text-center{text-align:center}.text-left{text-align:left}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[16px\]{font-size:16px}.text-\[17px\]{font-size:17px}.text-\[18px\]{font-size:18px}.text-\[22px\]{font-size:22px}.text-\[26px\]{font-size:26px}.leading-\[1\.2\]{--tw-leading:1.2;line-height:1.2}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-\[-0\.01em\]{--tw-tracking:-.01em;letter-spacing:-.01em}.tracking-\[-0\.02em\]{--tw-tracking:-.02em;letter-spacing:-.02em}.text-\[\#141b2d\]{color:#141b2d}.text-\[\#c62828\]{color:#c62828}.text-accent{color:var(--color-accent)}.text-danger{color:var(--color-danger)}.text-muted{color:var(--color-muted)}.text-on-accent{color:var(--color-on-accent)}.text-side-text{color:var(--color-side-text)}.text-text{color:var(--color-text)}.text-white{color:var(--color-white)}.no-underline{text-decoration-line:none}.shadow-card{--tw-shadow:var(--c-shadow-card);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-login{--tw-shadow:0 24px 60px var(--tw-shadow-color,#00000059);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.first\:pl-5:first-child{padding-left:calc(var(--spacing) * 5)}.last\:pr-5:last-child{padding-right:calc(var(--spacing) * 5)}@media(hover:hover){.hover\:bg-hover:hover{background-color:var(--color-hover)}.hover\:bg-side-hover:hover{background-color:var(--color-side-hover)}.hover\:text-text:hover{color:var(--color-text)}.hover\:text-white:hover{color:var(--color-white)}}.disabled\:opacity-60:disabled{opacity:.6}}@font-face{font-family:DM Sans;font-style:normal;font-display:swap;font-weight:400;src:url(./dm-sans-latin-ext-400-normal-BtiwyxMk.woff2)format("woff2"),url(./dm-sans-latin-ext-400-normal-BjWJ59Pq.woff)format("woff");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:DM Sans;font-style:normal;font-display:swap;font-weight:400;src:url(./dm-sans-latin-400-normal-CW0RaeGs.woff2)format("woff2"),url(./dm-sans-latin-400-normal-BwCSEQnW.woff)format("woff");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:DM Sans;font-style:normal;font-display:swap;font-weight:500;src:url(./dm-sans-latin-ext-500-normal-BJfUCQsA.woff2)format("woff2"),url(./dm-sans-latin-ext-500-normal-DR84L5F-.woff)format("woff");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:DM Sans;font-style:normal;font-display:swap;font-weight:500;src:url(./dm-sans-latin-500-normal-B9HHJjqV.woff2)format("woff2"),url(./dm-sans-latin-500-normal-Dr3UlScf.woff)format("woff");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:DM Sans;font-style:normal;font-display:swap;font-weight:600;src:url(./dm-sans-latin-ext-600-normal-4vooXBpG.woff2)format("woff2"),url(./dm-sans-latin-ext-600-normal-DRtaDpgU.woff)format("woff");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:DM Sans;font-style:normal;font-display:swap;font-weight:600;src:url(./dm-sans-latin-600-normal-Aqo67rzb.woff2)format("woff2"),url(./dm-sans-latin-600-normal-BmdmIIQ2.woff)format("woff");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:DM Sans;font-style:normal;font-display:swap;font-weight:700;src:url(./dm-sans-latin-ext-700-normal-CJIcxD6K.woff2)format("woff2"),url(./dm-sans-latin-ext-700-normal-BLI3TTWz.woff)format("woff");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:DM Sans;font-style:normal;font-display:swap;font-weight:700;src:url(./dm-sans-latin-700-normal-DvUfVpUG.woff2)format("woff2"),url(./dm-sans-latin-700-normal-CUSSCpQX.woff)format("woff");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:DM Mono;font-style:normal;font-display:swap;font-weight:400;src:url(./dm-mono-latin-ext-400-normal-C2zvOubV.woff2)format("woff2"),url(./dm-mono-latin-ext-400-normal-1aZr6b2b.woff)format("woff");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:DM Mono;font-style:normal;font-display:swap;font-weight:400;src:url(./dm-mono-latin-400-normal-4GdczIuU.woff2)format("woff2"),url(./dm-mono-latin-400-normal--0xN8mdc.woff)format("woff");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:DM Mono;font-style:normal;font-display:swap;font-weight:500;src:url(./dm-mono-latin-ext-500-normal-BtRyHRi6.woff2)format("woff2"),url(./dm-mono-latin-ext-500-normal-Dw3M13d8.woff)format("woff");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:DM Mono;font-style:normal;font-display:swap;font-weight:500;src:url(./dm-mono-latin-500-normal-DRMDZjhP.woff2)format("woff2"),url(./dm-mono-latin-500-normal-CN8Miw6E.woff)format("woff");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--c-bg:#f4f6f9;--c-surface:#fff;--c-subtle:#f3f5f8;--c-border:#e6e9ef;--c-border-strong:#d2d8e2;--c-text:#141b2d;--c-muted:#566175;--c-placeholder:#6b7588;--c-primary:#22304d;--c-on-primary:#fff;--c-danger:#c62828;--c-danger-soft:#fdf0f0;--c-ok-bg:#e3f4e8;--c-ok-text:#1c6b35;--c-ring:#fcc4288c;--c-hover:#f1f3f7;--c-sel:#fdf3cf;--c-skel:#eceff4;--c-overlay:#141b2d80;--c-side:#1d2740;--c-side-border:transparent;--c-shadow-card:0 1px 2px #141b2d0a, 0 4px 16px #141b2d0d}.dark{--c-bg:#111726;--c-surface:#182033;--c-subtle:#1f283d;--c-border:#29334b;--c-border-strong:#3a4661;--c-text:#eef1f6;--c-muted:#a9b3c6;--c-placeholder:#8a95ab;--c-primary:#fcd34d;--c-on-primary:#1b2540;--c-danger:#f58a8a;--c-danger-soft:#3a1d24;--c-ok-bg:#173826;--c-ok-text:#8fdfa8;--c-ring:#fcd34d73;--c-hover:#212b42;--c-sel:#3a3622;--c-skel:#263049;--c-overlay:#050810b3;--c-side:#0d1320;--c-side-border:#222b40;--c-shadow-card:none}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000} diff --git a/boardwalk/dist/assets/index-ZNCn30hM.js b/boardwalk/dist/assets/index-ZNCn30hM.js new file mode 100644 index 0000000..f674cd4 --- /dev/null +++ b/boardwalk/dist/assets/index-ZNCn30hM.js @@ -0,0 +1 @@ +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))r(s);new MutationObserver(s=>{for(const o of s)if(o.type==="childList")for(const i of o.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&r(i)}).observe(document,{childList:!0,subtree:!0});function n(s){const o={};return s.integrity&&(o.integrity=s.integrity),s.referrerPolicy&&(o.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?o.credentials="include":s.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function r(s){if(s.ep)return;s.ep=!0;const o=n(s);fetch(s.href,o)}})();function Pr(e){const t=Object.create(null);for(const n of e.split(","))t[n]=1;return n=>n in t}const re={},Lt=[],it=()=>{},fo=()=>!1,In=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),Ln=e=>e.startsWith("onUpdate:"),Ee=Object.assign,Tr=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},Wi=Object.prototype.hasOwnProperty,Y=(e,t)=>Wi.call(e,t),F=Array.isArray,Dt=e=>yn(e)==="[object Map]",ho=e=>yn(e)==="[object Set]",rs=e=>yn(e)==="[object Date]",U=e=>typeof e=="function",ae=e=>typeof e=="string",Ke=e=>typeof e=="symbol",Z=e=>e!==null&&typeof e=="object",po=e=>(Z(e)||U(e))&&U(e.then)&&U(e.catch),go=Object.prototype.toString,yn=e=>go.call(e),Gi=e=>yn(e).slice(8,-1),mo=e=>yn(e)==="[object Object]",Mr=e=>ae(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,tn=Pr(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),Dn=e=>{const t=Object.create(null);return(n=>t[n]||(t[n]=e(n)))},Qi=/-\w/g,$e=Dn(e=>e.replace(Qi,t=>t.slice(1).toUpperCase())),Ji=/\B([A-Z])/g,Tt=Dn(e=>e.replace(Ji,"-$1").toLowerCase()),Fn=Dn(e=>e.charAt(0).toUpperCase()+e.slice(1)),Xn=Dn(e=>e?`on${Fn(e)}`:""),ot=(e,t)=>!Object.is(e,t),Sn=(e,...t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:r,value:n})},$r=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let ss;const Hn=()=>ss||(ss=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Or(e){if(F(e)){const t={};for(let n=0;n{if(n){const r=n.split(Xi);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t}function wt(e){let t="";if(ae(e))t=e;else if(F(e))for(let n=0;n!!(e&&e.__v_isRef===!0),de=e=>ae(e)?e:e==null?"":F(e)||Z(e)&&(e.toString===go||!U(e.toString))?vo(e)?de(e.value):JSON.stringify(e,bo,2):String(e),bo=(e,t)=>vo(t)?bo(e,t.value):Dt(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[r,s],o)=>(n[Zn(r,o)+" =>"]=s,n),{})}:ho(t)?{[`Set(${t.size})`]:[...t.values()].map(n=>Zn(n))}:Ke(t)?Zn(t):Z(t)&&!F(t)&&!mo(t)?String(t):t,Zn=(e,t="")=>{var n;return Ke(e)?`Symbol(${(n=e.description)!=null?n:t})`:e};let xe;class sl{constructor(t=!1){this.detached=t,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this._warnOnRun=!0,this.__v_skip=!0,!t&&xe&&(xe.active?(this.parent=xe,this.index=(xe.scopes||(xe.scopes=[])).push(this)-1):(this._active=!1,this._warnOnRun=!1))}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,n;if(this.scopes)for(t=0,n=this.scopes.length;t0&&--this._on===0){if(xe===this)xe=this.prevScope;else{let t=xe;for(;t;){if(t.prevScope===this){t.prevScope=this.prevScope;break}t=t.prevScope}}this.prevScope=void 0}}stop(t){if(this._active){this._active=!1;let n,r;for(n=0,r=this.effects.length;n0)return;if(rn){let t=rn;for(rn=void 0;t;){const n=t.next;t.next=void 0,t.flags&=-9,t=n}}let e;for(;nn;){let t=nn;for(nn=void 0;t;){const n=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(r){e||(e=r)}t=n}}if(e)throw e}function Eo(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function Ro(e){let t,n=e.depsTail,r=n;for(;r;){const s=r.prevDep;r.version===-1?(r===n&&(n=s),Lr(r),il(r)):t=r,r.dep.activeLink=r.prevActiveLink,r.prevActiveLink=void 0,r=s}e.deps=t,e.depsTail=n}function hr(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(Co(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function Co(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===un)||(e.globalVersion=un,!e.isSSR&&e.flags&128&&(!e.deps&&!e._dirty||!hr(e))))return;e.flags|=2;const t=e.dep,n=ie,r=Be;ie=e,Be=!0;try{Eo(e);const s=e.fn(e._value);(t.version===0||ot(s,e._value))&&(e.flags|=128,e._value=s,t.version++)}catch(s){throw t.version++,s}finally{ie=n,Be=r,Ro(e),e.flags&=-3}}function Lr(e,t=!1){const{dep:n,prevSub:r,nextSub:s}=e;if(r&&(r.nextSub=s,e.prevSub=void 0),s&&(s.prevSub=r,e.nextSub=void 0),n.subs===e&&(n.subs=r,!r&&n.computed)){n.computed.flags&=-5;for(let o=n.computed.deps;o;o=o.nextDep)Lr(o,!0)}!t&&!--n.sc&&n.map&&n.map.delete(n.key)}function il(e){const{prevDep:t,nextDep:n}=e;t&&(t.nextDep=n,e.prevDep=void 0),n&&(n.prevDep=t,e.nextDep=void 0)}let Be=!0;const ko=[];function pt(){ko.push(Be),Be=!1}function gt(){const e=ko.pop();Be=e===void 0?!0:e}function os(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const n=ie;ie=void 0;try{t()}finally{ie=n}}}let un=0;class ll{constructor(t,n){this.sub=t,this.dep=n,this.version=n.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class Dr{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0,this.__v_skip=!0}track(t){if(!ie||!Be||ie===this.computed)return;let n=this.activeLink;if(n===void 0||n.sub!==ie)n=this.activeLink=new ll(ie,this),ie.deps?(n.prevDep=ie.depsTail,ie.depsTail.nextDep=n,ie.depsTail=n):ie.deps=ie.depsTail=n,Ao(n);else if(n.version===-1&&(n.version=this.version,n.nextDep)){const r=n.nextDep;r.prevDep=n.prevDep,n.prevDep&&(n.prevDep.nextDep=r),n.prevDep=ie.depsTail,n.nextDep=void 0,ie.depsTail.nextDep=n,ie.depsTail=n,ie.deps===n&&(ie.deps=r)}return n}trigger(t){this.version++,un++,this.notify(t)}notify(t){Nr();try{for(let n=this.subs;n;n=n.prevSub)n.sub.notify()&&n.sub.dep.notify()}finally{Ir()}}}function Ao(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let r=t.deps;r;r=r.nextDep)Ao(r)}const n=e.dep.subs;n!==e&&(e.prevSub=n,n&&(n.nextSub=e)),e.dep.subs=e}}const pr=new WeakMap,At=Symbol(""),gr=Symbol(""),fn=Symbol("");function Re(e,t,n){if(Be&&ie){let r=pr.get(e);r||pr.set(e,r=new Map);let s=r.get(n);s||(r.set(n,s=new Dr),s.map=r,s.key=n),s.track()}}function dt(e,t,n,r,s,o){const i=pr.get(e);if(!i){un++;return}const l=c=>{c&&c.trigger()};if(Nr(),t==="clear")i.forEach(l);else{const c=F(e),f=c&&Mr(n);if(c&&n==="length"){const u=Number(r);i.forEach((h,p)=>{(p==="length"||p===fn||!Ke(p)&&p>=u)&&l(h)})}else switch((n!==void 0||i.has(void 0))&&l(i.get(n)),f&&l(i.get(fn)),t){case"add":c?f&&l(i.get("length")):(l(i.get(At)),Dt(e)&&l(i.get(gr)));break;case"delete":c||(l(i.get(At)),Dt(e)&&l(i.get(gr)));break;case"set":Dt(e)&&l(i.get(At));break}}Ir()}function Mt(e){const t=J(e);return t===e?t:(Re(t,"iterate",fn),Ve(e)?t:t.map(We))}function Un(e){return Re(e=J(e),"iterate",fn),e}function rt(e,t){return mt(e)?qt(Pt(e)?We(t):t):We(t)}const cl={__proto__:null,[Symbol.iterator](){return tr(this,Symbol.iterator,e=>rt(this,e))},concat(...e){return Mt(this).concat(...e.map(t=>F(t)?Mt(t):t))},entries(){return tr(this,"entries",e=>(e[1]=rt(this,e[1]),e))},every(e,t){return ct(this,"every",e,t,void 0,arguments)},filter(e,t){return ct(this,"filter",e,t,n=>n.map(r=>rt(this,r)),arguments)},find(e,t){return ct(this,"find",e,t,n=>rt(this,n),arguments)},findIndex(e,t){return ct(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){return ct(this,"findLast",e,t,n=>rt(this,n),arguments)},findLastIndex(e,t){return ct(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){return ct(this,"forEach",e,t,void 0,arguments)},includes(...e){return nr(this,"includes",e)},indexOf(...e){return nr(this,"indexOf",e)},join(e){return Mt(this).join(e)},lastIndexOf(...e){return nr(this,"lastIndexOf",e)},map(e,t){return ct(this,"map",e,t,void 0,arguments)},pop(){return Qt(this,"pop")},push(...e){return Qt(this,"push",e)},reduce(e,...t){return is(this,"reduce",e,t)},reduceRight(e,...t){return is(this,"reduceRight",e,t)},shift(){return Qt(this,"shift")},some(e,t){return ct(this,"some",e,t,void 0,arguments)},splice(...e){return Qt(this,"splice",e)},toReversed(){return Mt(this).toReversed()},toSorted(e){return Mt(this).toSorted(e)},toSpliced(...e){return Mt(this).toSpliced(...e)},unshift(...e){return Qt(this,"unshift",e)},values(){return tr(this,"values",e=>rt(this,e))}};function tr(e,t,n){const r=Un(e),s=r[t]();return r!==e&&!Ve(e)&&(s._next=s.next,s.next=()=>{const o=s._next();return o.done||(o.value=n(o.value)),o}),s}const al=Array.prototype;function ct(e,t,n,r,s,o){const i=Un(e),l=i!==e&&!Ve(e),c=i[t];if(c!==al[t]){const h=c.apply(e,o);return l?We(h):h}let f=n;i!==e&&(l?f=function(h,p){return n.call(this,rt(e,h),p,e)}:n.length>2&&(f=function(h,p){return n.call(this,h,p,e)}));const u=c.call(i,f,r);return l&&s?s(u):u}function is(e,t,n,r){const s=Un(e),o=s!==e&&!Ve(e);let i=n,l=!1;s!==e&&(o?(l=r.length===0,i=function(f,u,h){return l&&(l=!1,f=rt(e,f)),n.call(this,f,rt(e,u),h,e)}):n.length>3&&(i=function(f,u,h){return n.call(this,f,u,h,e)}));const c=s[t](i,...r);return l?rt(e,c):c}function nr(e,t,n){const r=J(e);Re(r,"iterate",fn);const s=r[t](...n);return(s===-1||s===!1)&&Ur(n[0])?(n[0]=J(n[0]),r[t](...n)):s}function Qt(e,t,n=[]){pt(),Nr();const r=J(e)[t].apply(e,n);return Ir(),gt(),r}const ul=Pr("__proto__,__v_isRef,__isVue"),Po=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Ke));function fl(e){Ke(e)||(e=String(e));const t=J(this);return Re(t,"has",e),t.hasOwnProperty(e)}class To{constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}get(t,n,r){if(n==="__v_skip")return t.__v_skip;const s=this._isReadonly,o=this._isShallow;if(n==="__v_isReactive")return!s;if(n==="__v_isReadonly")return s;if(n==="__v_isShallow")return o;if(n==="__v_raw")return r===(s?o?xl:jo:o?Oo:$o).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(r)?t:void 0;const i=F(t);if(!s){let c;if(i&&(c=cl[n]))return c;if(n==="hasOwnProperty")return fl}const l=Reflect.get(t,n,ke(t)?t:r);if((Ke(n)?Po.has(n):ul(n))||(s||Re(t,"get",n),o))return l;if(ke(l)){const c=i&&Mr(n)?l:l.value;return s&&Z(c)?Cn(c):c}return Z(l)?s?Cn(l):qn(l):l}}class Mo extends To{constructor(t=!1){super(!1,t)}set(t,n,r,s){let o=t[n];const i=F(t)&&Mr(n);if(!this._isShallow){const f=mt(o);if(!Ve(r)&&!mt(r)&&(o=J(o),r=J(r)),!i&&ke(o)&&!ke(r))return f||(o.value=r),!0}const l=i?Number(n)e,bn=e=>Reflect.getPrototypeOf(e);function ml(e,t,n){return function(...r){const s=this.__v_raw,o=J(s),i=Dt(o),l=e==="entries"||e===Symbol.iterator&&i,c=e==="keys"&&i,f=s[e](...r),u=n?mr:t?qt:We;return!t&&Re(o,"iterate",c?gr:At),Ee(Object.create(f),{next(){const{value:h,done:p}=f.next();return p?{value:h,done:p}:{value:l?[u(h[0]),u(h[1])]:u(h),done:p}}})}}function xn(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function yl(e,t){const n={get(s){const o=this.__v_raw,i=J(o),l=J(s);e||(ot(s,l)&&Re(i,"get",s),Re(i,"get",l));const{has:c}=bn(i),f=t?mr:e?qt:We;if(c.call(i,s))return f(o.get(s));if(c.call(i,l))return f(o.get(l));o!==i&&o.get(s)},get size(){const s=this.__v_raw;return!e&&Re(J(s),"iterate",At),s.size},has(s){const o=this.__v_raw,i=J(o),l=J(s);return e||(ot(s,l)&&Re(i,"has",s),Re(i,"has",l)),s===l?o.has(s):o.has(s)||o.has(l)},forEach(s,o){const i=this,l=i.__v_raw,c=J(l),f=t?mr:e?qt:We;return!e&&Re(c,"iterate",At),l.forEach((u,h)=>s.call(o,f(u),f(h),i))}};return Ee(n,e?{add:xn("add"),set:xn("set"),delete:xn("delete"),clear:xn("clear")}:{add(s){const o=J(this),i=bn(o),l=J(s),c=!t&&!Ve(s)&&!mt(s)?l:s;return i.has.call(o,c)||ot(s,c)&&i.has.call(o,s)||ot(l,c)&&i.has.call(o,l)||(o.add(c),dt(o,"add",c,c)),this},set(s,o){!t&&!Ve(o)&&!mt(o)&&(o=J(o));const i=J(this),{has:l,get:c}=bn(i);let f=l.call(i,s);f||(s=J(s),f=l.call(i,s));const u=c.call(i,s);return i.set(s,o),f?ot(o,u)&&dt(i,"set",s,o):dt(i,"add",s,o),this},delete(s){const o=J(this),{has:i,get:l}=bn(o);let c=i.call(o,s);c||(s=J(s),c=i.call(o,s)),l&&l.call(o,s);const f=o.delete(s);return c&&dt(o,"delete",s,void 0),f},clear(){const s=J(this),o=s.size!==0,i=s.clear();return o&&dt(s,"clear",void 0,void 0),i}}),["keys","values","entries",Symbol.iterator].forEach(s=>{n[s]=ml(s,e,t)}),n}function Fr(e,t){const n=yl(e,t);return(r,s,o)=>s==="__v_isReactive"?!e:s==="__v_isReadonly"?e:s==="__v_raw"?r:Reflect.get(Y(n,s)&&s in r?n:r,s,o)}const _l={get:Fr(!1,!1)},vl={get:Fr(!1,!0)},bl={get:Fr(!0,!1)};const $o=new WeakMap,Oo=new WeakMap,jo=new WeakMap,xl=new WeakMap;function wl(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function qn(e){return mt(e)?e:Hr(e,!1,hl,_l,$o)}function No(e){return Hr(e,!1,gl,vl,Oo)}function Cn(e){return Hr(e,!0,pl,bl,jo)}function Hr(e,t,n,r,s){if(!Z(e)||e.__v_raw&&!(t&&e.__v_isReactive)||e.__v_skip||!Object.isExtensible(e))return e;const o=s.get(e);if(o)return o;const i=wl(Gi(e));if(i===0)return e;const l=new Proxy(e,i===2?r:n);return s.set(e,l),l}function Pt(e){return mt(e)?Pt(e.__v_raw):!!(e&&e.__v_isReactive)}function mt(e){return!!(e&&e.__v_isReadonly)}function Ve(e){return!!(e&&e.__v_isShallow)}function Ur(e){return e?!!e.__v_raw:!1}function J(e){const t=e&&e.__v_raw;return t?J(t):e}function Sl(e){return!Y(e,"__v_skip")&&Object.isExtensible(e)&&yo(e,"__v_skip",!0),e}const We=e=>Z(e)?qn(e):e,qt=e=>Z(e)?Cn(e):e;function ke(e){return e?e.__v_isRef===!0:!1}function Ie(e){return Io(e,!1)}function El(e){return Io(e,!0)}function Io(e,t){return ke(e)?e:new Rl(e,t)}class Rl{constructor(t,n){this.dep=new Dr,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=n?t:J(t),this._value=n?t:We(t),this.__v_isShallow=n}get value(){return this.dep.track(),this._value}set value(t){const n=this._rawValue,r=this.__v_isShallow||Ve(t)||mt(t);t=r?t:J(t),ot(t,n)&&(this._rawValue=t,this._value=r?t:We(t),this.dep.trigger())}}function X(e){return ke(e)?e.value:e}const Cl={get:(e,t,n)=>t==="__v_raw"?e:X(Reflect.get(e,t,n)),set:(e,t,n,r)=>{const s=e[t];return ke(s)&&!ke(n)?(s.value=n,!0):Reflect.set(e,t,n,r)}};function Lo(e){return Pt(e)?e:new Proxy(e,Cl)}class kl{constructor(t,n,r){this.fn=t,this.setter=n,this._value=void 0,this.dep=new Dr(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=un-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!n,this.isSSR=r}notify(){if(this.flags|=16,!(this.flags&8)&&ie!==this)return So(this,!0),!0}get value(){const t=this.dep.track();return Co(this),t&&(t.version=this.dep.version),this._value}set value(t){this.setter&&this.setter(t)}}function Al(e,t,n=!1){let r,s;return U(e)?r=e:(r=e.get,s=e.set),new kl(r,s,n)}const wn={},kn=new WeakMap;let Ct;function Pl(e,t=!1,n=Ct){if(n){let r=kn.get(n);r||kn.set(n,r=[]),r.push(e)}}function Tl(e,t,n=re){const{immediate:r,deep:s,once:o,scheduler:i,augmentJob:l,call:c}=n,f=$=>s?$:Ve($)||s===!1||s===0?ht($,1):ht($);let u,h,p,g,R=!1,w=!1;if(ke(e)?(h=()=>e.value,R=Ve(e)):Pt(e)?(h=()=>f(e),R=!0):F(e)?(w=!0,R=e.some($=>Pt($)||Ve($)),h=()=>e.map($=>{if(ke($))return $.value;if(Pt($))return f($);if(U($))return c?c($,2):$()})):U(e)?t?h=c?()=>c(e,2):e:h=()=>{if(p){pt();try{p()}finally{gt()}}const $=Ct;Ct=u;try{return c?c(e,3,[g]):e(g)}finally{Ct=$}}:h=it,t&&s){const $=h,G=s===!0?1/0:s;h=()=>ht($(),G)}const I=ol(),O=()=>{u.stop(),I&&I.active&&Tr(I.effects,u)};if(o&&t){const $=t;t=(...G)=>{$(...G),O()}}let k=w?new Array(e.length).fill(wn):wn;const M=$=>{if(!(!(u.flags&1)||!u.dirty&&!$))if(t){const G=u.run();if(s||R||(w?G.some((me,se)=>ot(me,k[se])):ot(G,k))){p&&p();const me=Ct;Ct=u;try{const se=[G,k===wn?void 0:w&&k[0]===wn?[]:k,g];k=G,c?c(t,3,se):t(...se)}finally{Ct=me}}}else u.run()};return l&&l(M),u=new xo(h),u.scheduler=i?()=>i(M,!1):M,g=$=>Pl($,!1,u),p=u.onStop=()=>{const $=kn.get(u);if($){if(c)c($,4);else for(const G of $)G();kn.delete(u)}},t?r?M(!0):k=u.run():i?i(M.bind(null,!0),!0):u.run(),O.pause=u.pause.bind(u),O.resume=u.resume.bind(u),O.stop=O,O}function ht(e,t=1/0,n){if(t<=0||!Z(e)||e.__v_skip||(n=n||new Map,(n.get(e)||0)>=t))return e;if(n.set(e,t),t--,ke(e))ht(e.value,t,n);else if(F(e))for(let r=0;r{ht(r,t,n)});else if(mo(e)){for(const r in e)ht(e[r],t,n);for(const r of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,r)&&ht(e[r],t,n)}return e}function _n(e,t,n,r){try{return r?e(...r):e()}catch(s){Vn(s,t,n)}}function Ge(e,t,n,r){if(U(e)){const s=_n(e,t,n,r);return s&&po(s)&&s.catch(o=>{Vn(o,t,n)}),s}if(F(e)){const s=[];for(let o=0;o>>1,s=Me[r],o=dn(s);o=dn(n)?Me.push(e):Me.splice($l(t),0,e),e.flags|=1,Ho()}}function Ho(){An||(An=Do.then(qo))}function Ol(e){F(e)?Ft.push(...e):bt&&e.id===-1?bt.splice(Ot+1,0,e):e.flags&1||(Ft.push(e),e.flags|=1),Ho()}function ls(e,t,n=nt+1){for(;ndn(n)-dn(r));if(Ft.length=0,bt){bt.push(...t);return}for(bt=t,Ot=0;Ote.id==null?e.flags&2?-1:1/0:e.id;function qo(e){try{for(nt=0;nt{r._d&&$n(-1);const o=Pn(t);let i;try{i=e(...s)}finally{Pn(o),r._d&&$n(1)}return i};return r._n=!0,r._c=!0,r._d=!0,r}function cs(e,t){if(Se===null)return e;const n=Gn(Se),r=e.dirs||(e.dirs=[]);for(let s=0;s1)return n&&U(t)?t.call(r&&r.proxy):t}}const jl=Symbol.for("v-scx"),Nl=()=>ze(jl);function sn(e,t,n){return zo(e,t,n)}function zo(e,t,n=re){const{immediate:r,deep:s,flush:o,once:i}=n,l=Ee({},n),c=t&&r||!t&&o!=="post";let f;if(gn){if(o==="sync"){const g=Nl();f=g.__watcherHandles||(g.__watcherHandles=[])}else if(!c){const g=()=>{};return g.stop=it,g.resume=it,g.pause=it,g}}const u=Ce;l.call=(g,R,w)=>Ge(g,u,R,w);let h=!1;o==="post"?l.scheduler=g=>{Ne(g,u&&u.suspense)}:o!=="sync"&&(h=!0,l.scheduler=(g,R)=>{R?g():qr(g)}),l.augmentJob=g=>{t&&(g.flags|=4),h&&(g.flags|=2,u&&(g.id=u.uid,g.i=u))};const p=Tl(e,t,l);return gn&&(f?f.push(p):c&&p()),p}function Il(e,t,n){const r=this.proxy,s=ae(e)?e.includes(".")?Bo(r,e):()=>r[e]:e.bind(r,r);let o;U(t)?o=t:(o=t.handler,n=t);const i=vn(this),l=zo(s,o.bind(r),n);return i(),l}function Bo(e,t){const n=t.split(".");return()=>{let r=e;for(let s=0;se.__isTeleport,rr=Symbol("_leaveCb");function Vr(e,t){e.shapeFlag&6&&e.component?(e.transition=t,Vr(e.component.subTree,t)):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function lt(e,t){return U(e)?Ee({name:e.name},t,{setup:e}):e}function Ko(e){e.ids=[e.ids[0]+e.ids[2]+++"-",0,0]}function as(e,t){let n;return!!((n=Object.getOwnPropertyDescriptor(e,t))&&!n.configurable)}const Tn=new WeakMap;function on(e,t,n,r,s=!1){if(F(e)){e.forEach((w,I)=>on(w,t&&(F(t)?t[I]:t),n,r,s));return}if(Ht(r)&&!s){r.shapeFlag&512&&r.type.__asyncResolved&&r.component.subTree.component&&on(e,t,n,r.component.subTree);return}const o=r.shapeFlag&4?Gn(r.component):r.el,i=s?null:o,{i:l,r:c}=e,f=t&&t.r,u=l.refs===re?l.refs={}:l.refs,h=l.setupState,p=J(h),g=h===re?fo:w=>as(u,w)?!1:Y(p,w),R=(w,I)=>!(I&&as(u,I));if(f!=null&&f!==c){if(us(t),ae(f))u[f]=null,g(f)&&(h[f]=null);else if(ke(f)){const w=t;R(f,w.k)&&(f.value=null),w.k&&(u[w.k]=null)}}if(U(c))_n(c,l,12,[i,u]);else{const w=ae(c),I=ke(c);if(w||I){const O=()=>{if(e.f){const k=w?g(c)?h[c]:u[c]:R()||!e.k?c.value:u[e.k];if(s)F(k)&&Tr(k,o);else if(F(k))k.includes(o)||k.push(o);else if(w)u[c]=[o],g(c)&&(h[c]=u[c]);else{const M=[o];R(c,e.k)&&(c.value=M),e.k&&(u[e.k]=M)}}else w?(u[c]=i,g(c)&&(h[c]=i)):I&&(R(c,e.k)&&(c.value=i),e.k&&(u[e.k]=i))};if(i){const k=()=>{O(),Tn.delete(e)};k.id=-1,Tn.set(e,k),Ne(k,n)}else us(e),O()}}}function us(e){const t=Tn.get(e);t&&(t.flags|=8,Tn.delete(e))}Hn().requestIdleCallback;Hn().cancelIdleCallback;const Ht=e=>!!e.type.__asyncLoader,Wo=e=>e.type.__isKeepAlive;function Fl(e,t){Go(e,"a",t)}function Hl(e,t){Go(e,"da",t)}function Go(e,t,n=Ce){const r=e.__wdc||(e.__wdc=()=>{let s=n;for(;s;){if(s.isDeactivated)return;s=s.parent}return e()});if(Bn(t,r,n),n){let s=n.parent;for(;s&&s.parent;)Wo(s.parent.vnode)&&Ul(r,t,n,s),s=s.parent}}function Ul(e,t,n,r){const s=Bn(t,e,r,!0);Qo(()=>{Tr(r[t],s)},n)}function Bn(e,t,n=Ce,r=!1){if(n){const s=n[e]||(n[e]=[]),o=t.__weh||(t.__weh=(...i)=>{pt();const l=vn(n),c=Ge(t,n,e,i);return l(),gt(),c});return r?s.unshift(o):s.push(o),o}}const _t=e=>(t,n=Ce)=>{(!gn||e==="sp")&&Bn(e,(...r)=>t(...r),n)},ql=_t("bm"),Vl=_t("m"),zl=_t("bu"),Bl=_t("u"),Kl=_t("bum"),Qo=_t("um"),Wl=_t("sp"),Gl=_t("rtg"),Ql=_t("rtc");function Jl(e,t=Ce){Bn("ec",e,t)}const Yl="components",Jo=Symbol.for("v-ndc");function Yo(e){return ae(e)?Xl(Yl,e,!1)||e:e||Jo}function Xl(e,t,n=!0,r=!1){const s=Se||Ce;if(s){const o=s.type;{const l=Dc(o,!1);if(l&&(l===t||l===$e(t)||l===Fn($e(t))))return o}const i=fs(s[e]||o[e],t)||fs(s.appContext[e],t);return!i&&r?o:i}}function fs(e,t){return e&&(e[t]||e[$e(t)]||e[Fn($e(t))])}function kt(e,t,n,r){let s;const o=n,i=F(e);if(i||ae(e)){const l=i&&Pt(e);let c=!1,f=!1;l&&(c=!Ve(e),f=mt(e),e=Un(e)),s=new Array(e.length);for(let u=0,h=e.length;ut(l,c,void 0,o));else{const l=Object.keys(e);s=new Array(l.length);for(let c=0,f=l.length;c0;return V(),Qe(fe,null,[ge("slot",n,r)],f?-2:64)}let o=e[t];o&&o._c&&(o._d=!1),V();const i=o&&Xo(o(n)),l=n.key||i&&i.key,c=Qe(fe,{key:(l&&!Ke(l)?l:`_${t}`)+(!i&&r?"_fb":"")},i||[],i&&e._===1?64:-2);return c.scopeId&&(c.slotScopeIds=[c.scopeId+"-s"]),o&&o._c&&(o._d=!0),c}function Xo(e){return e.some(t=>pn(t)?!(t.type===yt||t.type===fe&&!Xo(t.children)):!0)?e:null}const yr=e=>e?vi(e)?Gn(e):yr(e.parent):null,ln=Ee(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>yr(e.parent),$root:e=>yr(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>ei(e),$forceUpdate:e=>e.f||(e.f=()=>{qr(e.update)}),$nextTick:e=>e.n||(e.n=Fo.bind(e.proxy)),$watch:e=>Il.bind(e)}),sr=(e,t)=>e!==re&&!e.__isScriptSetup&&Y(e,t),ec={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:n,setupState:r,data:s,props:o,accessCache:i,type:l,appContext:c}=e;if(t[0]!=="$"){const p=i[t];if(p!==void 0)switch(p){case 1:return r[t];case 2:return s[t];case 4:return n[t];case 3:return o[t]}else{if(sr(r,t))return i[t]=1,r[t];if(s!==re&&Y(s,t))return i[t]=2,s[t];if(Y(o,t))return i[t]=3,o[t];if(n!==re&&Y(n,t))return i[t]=4,n[t];_r&&(i[t]=0)}}const f=ln[t];let u,h;if(f)return t==="$attrs"&&Re(e.attrs,"get",""),f(e);if((u=l.__cssModules)&&(u=u[t]))return u;if(n!==re&&Y(n,t))return i[t]=4,n[t];if(h=c.config.globalProperties,Y(h,t))return h[t]},set({_:e},t,n){const{data:r,setupState:s,ctx:o}=e;return sr(s,t)?(s[t]=n,!0):r!==re&&Y(r,t)?(r[t]=n,!0):Y(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(o[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:r,appContext:s,props:o,type:i}},l){let c;return!!(n[l]||e!==re&&l[0]!=="$"&&Y(e,l)||sr(t,l)||Y(o,l)||Y(r,l)||Y(ln,l)||Y(s.config.globalProperties,l)||(c=i.__cssModules)&&c[l])},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:Y(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function ds(e){return F(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let _r=!0;function tc(e){const t=ei(e),n=e.proxy,r=e.ctx;_r=!1,t.beforeCreate&&hs(t.beforeCreate,e,"bc");const{data:s,computed:o,methods:i,watch:l,provide:c,inject:f,created:u,beforeMount:h,mounted:p,beforeUpdate:g,updated:R,activated:w,deactivated:I,beforeDestroy:O,beforeUnmount:k,destroyed:M,unmounted:$,render:G,renderTracked:me,renderTriggered:se,errorCaptured:He,serverPrefetch:Ue,expose:Oe,inheritAttrs:qe,components:Ye,directives:je,filters:St}=t;if(f&&nc(f,r,null),i)for(const K in i){const z=i[K];U(z)&&(r[K]=z.bind(n))}if(s){const K=s.call(n,n);Z(K)&&(e.data=qn(K))}if(_r=!0,o)for(const K in o){const z=o[K],ye=U(z)?z.bind(n,n):U(z.get)?z.get.bind(n,n):it,ee=!U(z)&&U(z.set)?z.set.bind(n):it,Le=ce({get:ye,set:ee});Object.defineProperty(r,K,{enumerable:!0,configurable:!0,get:()=>Le.value,set:_e=>Le.value=_e})}if(l)for(const K in l)Zo(l[K],r,n,K);if(c){const K=U(c)?c.call(n):c;Reflect.ownKeys(K).forEach(z=>{En(z,K[z])})}u&&hs(u,e,"c");function he(K,z){F(z)?z.forEach(ye=>K(ye.bind(n))):z&&K(z.bind(n))}if(he(ql,h),he(Vl,p),he(zl,g),he(Bl,R),he(Fl,w),he(Hl,I),he(Jl,He),he(Ql,me),he(Gl,se),he(Kl,k),he(Qo,$),he(Wl,Ue),F(Oe))if(Oe.length){const K=e.exposed||(e.exposed={});Oe.forEach(z=>{Object.defineProperty(K,z,{get:()=>n[z],set:ye=>n[z]=ye,enumerable:!0})})}else e.exposed||(e.exposed={});G&&e.render===it&&(e.render=G),qe!=null&&(e.inheritAttrs=qe),Ye&&(e.components=Ye),je&&(e.directives=je),Ue&&Ko(e)}function nc(e,t,n=it){F(e)&&(e=vr(e));for(const r in e){const s=e[r];let o;Z(s)?"default"in s?o=ze(s.from||r,s.default,!0):o=ze(s.from||r):o=ze(s),ke(o)?Object.defineProperty(t,r,{enumerable:!0,configurable:!0,get:()=>o.value,set:i=>o.value=i}):t[r]=o}}function hs(e,t,n){Ge(F(e)?e.map(r=>r.bind(t.proxy)):e.bind(t.proxy),t,n)}function Zo(e,t,n,r){let s=r.includes(".")?Bo(n,r):()=>n[r];if(ae(e)){const o=t[e];U(o)&&sn(s,o)}else if(U(e))sn(s,e.bind(n));else if(Z(e))if(F(e))e.forEach(o=>Zo(o,t,n,r));else{const o=U(e.handler)?e.handler.bind(n):t[e.handler];U(o)&&sn(s,o,e)}}function ei(e){const t=e.type,{mixins:n,extends:r}=t,{mixins:s,optionsCache:o,config:{optionMergeStrategies:i}}=e.appContext,l=o.get(t);let c;return l?c=l:!s.length&&!n&&!r?c=t:(c={},s.length&&s.forEach(f=>Mn(c,f,i,!0)),Mn(c,t,i)),Z(t)&&o.set(t,c),c}function Mn(e,t,n,r=!1){const{mixins:s,extends:o}=t;o&&Mn(e,o,n,!0),s&&s.forEach(i=>Mn(e,i,n,!0));for(const i in t)if(!(r&&i==="expose")){const l=rc[i]||n&&n[i];e[i]=l?l(e[i],t[i]):t[i]}return e}const rc={data:ps,props:gs,emits:gs,methods:Xt,computed:Xt,beforeCreate:Te,created:Te,beforeMount:Te,mounted:Te,beforeUpdate:Te,updated:Te,beforeDestroy:Te,beforeUnmount:Te,destroyed:Te,unmounted:Te,activated:Te,deactivated:Te,errorCaptured:Te,serverPrefetch:Te,components:Xt,directives:Xt,watch:oc,provide:ps,inject:sc};function ps(e,t){return t?e?function(){return Ee(U(e)?e.call(this,this):e,U(t)?t.call(this,this):t)}:t:e}function sc(e,t){return Xt(vr(e),vr(t))}function vr(e){if(F(e)){const t={};for(let n=0;nt==="modelValue"||t==="model-value"?e.modelModifiers:e[`${t}Modifiers`]||e[`${$e(t)}Modifiers`]||e[`${Tt(t)}Modifiers`];function ac(e,t,...n){if(e.isUnmounted)return;const r=e.vnode.props||re;let s=n;const o=t.startsWith("update:"),i=o&&cc(r,t.slice(7));i&&(i.trim&&(s=n.map(u=>ae(u)?u.trim():u)),i.number&&(s=n.map($r)));let l,c=r[l=Xn(t)]||r[l=Xn($e(t))];!c&&o&&(c=r[l=Xn(Tt(t))]),c&&Ge(c,e,6,s);const f=r[l+"Once"];if(f){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,Ge(f,e,6,s)}}const uc=new WeakMap;function ni(e,t,n=!1){const r=n?uc:t.emitsCache,s=r.get(e);if(s!==void 0)return s;const o=e.emits;let i={},l=!1;if(!U(e)){const c=f=>{const u=ni(f,t,!0);u&&(l=!0,Ee(i,u))};!n&&t.mixins.length&&t.mixins.forEach(c),e.extends&&c(e.extends),e.mixins&&e.mixins.forEach(c)}return!o&&!l?(Z(e)&&r.set(e,null),null):(F(o)?o.forEach(c=>i[c]=null):Ee(i,o),Z(e)&&r.set(e,i),i)}function Kn(e,t){return!e||!In(t)?!1:(t=t.slice(2).replace(/Once$/,""),Y(e,t[0].toLowerCase()+t.slice(1))||Y(e,Tt(t))||Y(e,t))}function ms(e){const{type:t,vnode:n,proxy:r,withProxy:s,propsOptions:[o],slots:i,attrs:l,emit:c,render:f,renderCache:u,props:h,data:p,setupState:g,ctx:R,inheritAttrs:w}=e,I=Pn(e);let O,k;try{if(n.shapeFlag&4){const $=s||r,G=$;O=st(f.call(G,$,u,h,g,p,R)),k=l}else{const $=t;O=st($.length>1?$(h,{attrs:l,slots:i,emit:c}):$(h,null)),k=t.props?l:fc(l)}}catch($){cn.length=0,Vn($,e,1),O=ge(yt)}let M=O;if(k&&w!==!1){const $=Object.keys(k),{shapeFlag:G}=M;$.length&&G&7&&(o&&$.some(Ln)&&(k=dc(k,o)),M=Vt(M,k,!1,!0))}return n.dirs&&(M=Vt(M,null,!1,!0),M.dirs=M.dirs?M.dirs.concat(n.dirs):n.dirs),n.transition&&Vr(M,n.transition),O=M,Pn(I),O}const fc=e=>{let t;for(const n in e)(n==="class"||n==="style"||In(n))&&((t||(t={}))[n]=e[n]);return t},dc=(e,t)=>{const n={};for(const r in e)(!Ln(r)||!(r.slice(9)in t))&&(n[r]=e[r]);return n};function hc(e,t,n){const{props:r,children:s,component:o}=e,{props:i,children:l,patchFlag:c}=t,f=o.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&c>=0){if(c&1024)return!0;if(c&16)return r?ys(r,i,f):!!i;if(c&8){const u=t.dynamicProps;for(let h=0;hObject.create(si),ii=e=>Object.getPrototypeOf(e)===si;function gc(e,t,n,r=!1){const s={},o=oi();e.propsDefaults=Object.create(null),li(e,t,s,o);for(const i in e.propsOptions[0])i in s||(s[i]=void 0);n?e.props=r?s:No(s):e.type.props?e.props=s:e.props=o,e.attrs=o}function mc(e,t,n,r){const{props:s,attrs:o,vnode:{patchFlag:i}}=e,l=J(s),[c]=e.propsOptions;let f=!1;if((r||i>0)&&!(i&16)){if(i&8){const u=e.vnode.dynamicProps;for(let h=0;h{c=!0;const[p,g]=ci(h,t,!0);Ee(i,p),g&&l.push(...g)};!n&&t.mixins.length&&t.mixins.forEach(u),e.extends&&u(e.extends),e.mixins&&e.mixins.forEach(u)}if(!o&&!c)return Z(e)&&r.set(e,Lt),Lt;if(F(o))for(let u=0;ue==="_"||e==="_ctx"||e==="$stable",Br=e=>F(e)?e.map(st):[st(e)],_c=(e,t,n)=>{if(t._n)return t;const r=zn((...s)=>Br(t(...s)),n);return r._c=!1,r},ai=(e,t,n)=>{const r=e._ctx;for(const s in e){if(zr(s))continue;const o=e[s];if(U(o))t[s]=_c(s,o,r);else if(o!=null){const i=Br(o);t[s]=()=>i}}},ui=(e,t)=>{const n=Br(t);e.slots.default=()=>n},fi=(e,t,n)=>{for(const r in t)(n||!zr(r))&&(e[r]=t[r])},vc=(e,t,n)=>{const r=e.slots=oi();if(e.vnode.shapeFlag&32){const s=t._;s?(fi(r,t,n),n&&yo(r,"_",s,!0)):ai(t,r)}else t&&ui(e,t)},bc=(e,t,n)=>{const{vnode:r,slots:s}=e;let o=!0,i=re;if(r.shapeFlag&32){const l=t._;l?n&&l===1?o=!1:fi(s,t,n):(o=!t.$stable,ai(t,s)),i=t}else t&&(ui(e,t),i={default:1});if(o)for(const l in s)!zr(l)&&i[l]==null&&delete s[l]},Ne=Rc;function xc(e){return wc(e)}function wc(e,t){const n=Hn();n.__VUE__=!0;const{insert:r,remove:s,patchProp:o,createElement:i,createText:l,createComment:c,setText:f,setElementText:u,parentNode:h,nextSibling:p,setScopeId:g=it,insertStaticContent:R}=e,w=(a,d,m,v=null,b=null,_=null,C=void 0,E=null,S=!!d.dynamicChildren)=>{if(a===d)return;a&&!Jt(a,d)&&(v=y(a),_e(a,b,_,!0),a=null),d.patchFlag===-2&&(S=!1,d.dynamicChildren=null);const{type:x,ref:L,shapeFlag:P}=d;switch(x){case Wn:I(a,d,m,v);break;case yt:O(a,d,m,v);break;case ir:a==null&&k(d,m,v,C);break;case fe:Ye(a,d,m,v,b,_,C,E,S);break;default:P&1?G(a,d,m,v,b,_,C,E,S):P&6?je(a,d,m,v,b,_,C,E,S):(P&64||P&128)&&x.process(a,d,m,v,b,_,C,E,S,j)}L!=null&&b?on(L,a&&a.ref,_,d||a,!d):L==null&&a&&a.ref!=null&&on(a.ref,null,_,a,!0)},I=(a,d,m,v)=>{if(a==null)r(d.el=l(d.children),m,v);else{const b=d.el=a.el;d.children!==a.children&&f(b,d.children)}},O=(a,d,m,v)=>{a==null?r(d.el=c(d.children||""),m,v):d.el=a.el},k=(a,d,m,v)=>{[a.el,a.anchor]=R(a.children,d,m,v,a.el,a.anchor)},M=({el:a,anchor:d},m,v)=>{let b;for(;a&&a!==d;)b=p(a),r(a,m,v),a=b;r(d,m,v)},$=({el:a,anchor:d})=>{let m;for(;a&&a!==d;)m=p(a),s(a),a=m;s(d)},G=(a,d,m,v,b,_,C,E,S)=>{if(d.type==="svg"?C="svg":d.type==="math"&&(C="mathml"),a==null)me(d,m,v,b,_,C,E,S);else{const x=a.el&&a.el._isVueCE?a.el:null;try{x&&x._beginPatch(),Ue(a,d,b,_,C,E,S)}finally{x&&x._endPatch()}}},me=(a,d,m,v,b,_,C,E)=>{let S,x;const{props:L,shapeFlag:P,transition:N,dirs:D}=a;if(S=a.el=i(a.type,_,L&&L.is,L),P&8?u(S,a.children):P&16&&He(a.children,S,null,v,b,or(a,_),C,E),D&&Et(a,null,v,"created"),se(S,a,a.scopeId,C,v),L){for(const ne in L)ne!=="value"&&!tn(ne)&&o(S,ne,null,L[ne],_,v);"value"in L&&o(S,"value",null,L.value,_),(x=L.onVnodeBeforeMount)&&tt(x,v,a)}D&&Et(a,null,v,"beforeMount");const W=Sc(b,N);W&&N.beforeEnter(S),r(S,d,m),((x=L&&L.onVnodeMounted)||W||D)&&Ne(()=>{x&&tt(x,v,a),W&&N.enter(S),D&&Et(a,null,v,"mounted")},b)},se=(a,d,m,v,b)=>{if(m&&g(a,m),v)for(let _=0;_{for(let x=S;x{const E=d.el=a.el;let{patchFlag:S,dynamicChildren:x,dirs:L}=d;S|=a.patchFlag&16;const P=a.props||re,N=d.props||re;let D;if(m&&Rt(m,!1),(D=N.onVnodeBeforeUpdate)&&tt(D,m,d,a),L&&Et(d,a,m,"beforeUpdate"),m&&Rt(m,!0),(P.innerHTML&&N.innerHTML==null||P.textContent&&N.textContent==null)&&u(E,""),x?Oe(a.dynamicChildren,x,E,m,v,or(d,b),_):C||z(a,d,E,null,m,v,or(d,b),_,!1),S>0){if(S&16)qe(E,P,N,m,b);else if(S&2&&P.class!==N.class&&o(E,"class",null,N.class,b),S&4&&o(E,"style",P.style,N.style,b),S&8){const W=d.dynamicProps;for(let ne=0;ne{D&&tt(D,m,d,a),L&&Et(d,a,m,"updated")},v)},Oe=(a,d,m,v,b,_,C)=>{for(let E=0;E{if(d!==m){if(d!==re)for(const _ in d)!tn(_)&&!(_ in m)&&o(a,_,d[_],null,b,v);for(const _ in m){if(tn(_))continue;const C=m[_],E=d[_];C!==E&&_!=="value"&&o(a,_,E,C,b,v)}"value"in m&&o(a,"value",d.value,m.value,b)}},Ye=(a,d,m,v,b,_,C,E,S)=>{const x=d.el=a?a.el:l(""),L=d.anchor=a?a.anchor:l("");let{patchFlag:P,dynamicChildren:N,slotScopeIds:D}=d;D&&(E=E?E.concat(D):D),a==null?(r(x,m,v),r(L,m,v),He(d.children||[],m,L,b,_,C,E,S)):P>0&&P&64&&N&&a.dynamicChildren&&a.dynamicChildren.length===N.length?(Oe(a.dynamicChildren,N,m,b,_,C,E),(d.key!=null||b&&d===b.subTree)&&di(a,d,!0)):z(a,d,m,L,b,_,C,E,S)},je=(a,d,m,v,b,_,C,E,S)=>{d.slotScopeIds=E,a==null?d.shapeFlag&512?b.ctx.activate(d,m,v,C,S):St(d,m,v,b,_,C,S):Ae(a,d,S)},St=(a,d,m,v,b,_,C)=>{const E=a.component=$c(a,v,b);if(Wo(a)&&(E.ctx.renderer=j),jc(E,!1,C),E.asyncDep){if(b&&b.registerDep(E,he,C),!a.el){const S=E.subTree=ge(yt);O(null,S,d,m),a.placeholder=S.el}}else he(E,a,d,m,b,_,C)},Ae=(a,d,m)=>{const v=d.component=a.component;if(hc(a,d,m))if(v.asyncDep&&!v.asyncResolved){K(v,d,m);return}else v.next=d,v.update();else d.el=a.el,v.vnode=d},he=(a,d,m,v,b,_,C)=>{const E=()=>{if(a.isMounted){let{next:P,bu:N,u:D,parent:W,vnode:ne}=a;{const Ze=hi(a);if(Ze){P&&(P.el=ne.el,K(a,P,C)),Ze.asyncDep.then(()=>{Ne(()=>{a.isUnmounted||x()},b)});return}}let te=P,pe;Rt(a,!1),P?(P.el=ne.el,K(a,P,C)):P=ne,N&&Sn(N),(pe=P.props&&P.props.onVnodeBeforeUpdate)&&tt(pe,W,P,ne),Rt(a,!0);const be=ms(a),Xe=a.subTree;a.subTree=be,w(Xe,be,h(Xe.el),y(Xe),a,b,_),P.el=be.el,te===null&&pc(a,be.el),D&&Ne(D,b),(pe=P.props&&P.props.onVnodeUpdated)&&Ne(()=>tt(pe,W,P,ne),b)}else{let P;const{el:N,props:D}=d,{bm:W,m:ne,parent:te,root:pe,type:be}=a,Xe=Ht(d);Rt(a,!1),W&&Sn(W),!Xe&&(P=D&&D.onVnodeBeforeMount)&&tt(P,te,d),Rt(a,!0);{pe.ce&&pe.ce._hasShadowRoot()&&pe.ce._injectChildStyle(be,a.parent?a.parent.type:void 0);const Ze=a.subTree=ms(a);w(null,Ze,m,v,a,b,_),d.el=Ze.el}if(ne&&Ne(ne,b),!Xe&&(P=D&&D.onVnodeMounted)){const Ze=d;Ne(()=>tt(P,te,Ze),b)}(d.shapeFlag&256||te&&Ht(te.vnode)&&te.vnode.shapeFlag&256)&&a.a&&Ne(a.a,b),a.isMounted=!0,d=m=v=null}};a.scope.on();const S=a.effect=new xo(E);a.scope.off();const x=a.update=S.run.bind(S),L=a.job=S.runIfDirty.bind(S);L.i=a,L.id=a.uid,S.scheduler=()=>qr(L),Rt(a,!0),x()},K=(a,d,m)=>{d.component=a;const v=a.vnode.props;a.vnode=d,a.next=null,mc(a,d.props,v,m),bc(a,d.children,m),pt(),ls(a),gt()},z=(a,d,m,v,b,_,C,E,S=!1)=>{const x=a&&a.children,L=a?a.shapeFlag:0,P=d.children,{patchFlag:N,shapeFlag:D}=d;if(N>0){if(N&128){ee(x,P,m,v,b,_,C,E,S);return}else if(N&256){ye(x,P,m,v,b,_,C,E,S);return}}D&8?(L&16&&ve(x,b,_),P!==x&&u(m,P)):L&16?D&16?ee(x,P,m,v,b,_,C,E,S):ve(x,b,_,!0):(L&8&&u(m,""),D&16&&He(P,m,v,b,_,C,E,S))},ye=(a,d,m,v,b,_,C,E,S)=>{a=a||Lt,d=d||Lt;const x=a.length,L=d.length,P=Math.min(x,L);let N;for(N=0;NL?ve(a,b,_,!0,!1,P):He(d,m,v,b,_,C,E,S,P)},ee=(a,d,m,v,b,_,C,E,S)=>{let x=0;const L=d.length;let P=a.length-1,N=L-1;for(;x<=P&&x<=N;){const D=a[x],W=d[x]=S?ft(d[x]):st(d[x]);if(Jt(D,W))w(D,W,m,null,b,_,C,E,S);else break;x++}for(;x<=P&&x<=N;){const D=a[P],W=d[N]=S?ft(d[N]):st(d[N]);if(Jt(D,W))w(D,W,m,null,b,_,C,E,S);else break;P--,N--}if(x>P){if(x<=N){const D=N+1,W=DN)for(;x<=P;)_e(a[x],b,_,!0),x++;else{const D=x,W=x,ne=new Map;for(x=W;x<=N;x++){const De=d[x]=S?ft(d[x]):st(d[x]);De.key!=null&&ne.set(De.key,x)}let te,pe=0;const be=N-W+1;let Xe=!1,Ze=0;const Gt=new Array(be);for(x=0;x=be){_e(De,b,_,!0);continue}let et;if(De.key!=null)et=ne.get(De.key);else for(te=W;te<=N;te++)if(Gt[te-W]===0&&Jt(De,d[te])){et=te;break}et===void 0?_e(De,b,_,!0):(Gt[et-W]=x+1,et>=Ze?Ze=et:Xe=!0,w(De,d[et],m,null,b,_,C,E,S),pe++)}const es=Xe?Ec(Gt):Lt;for(te=es.length-1,x=be-1;x>=0;x--){const De=W+x,et=d[De],ts=d[De+1],ns=De+1{const{el:_,type:C,transition:E,children:S,shapeFlag:x}=a;if(x&6){Le(a.component.subTree,d,m,v);return}if(x&128){a.suspense.move(d,m,v);return}if(x&64){C.move(a,d,m,j);return}if(C===fe){r(_,d,m);for(let P=0;PE.enter(_),b));else{const{leave:P,delayLeave:N,afterLeave:D}=E,W=()=>{a.ctx.isUnmounted?s(_):r(_,d,m)},ne=()=>{const te=_._isLeaving||!!_[rr];_._isLeaving&&_[rr](!0),E.persisted&&!te?W():P(_,()=>{W(),D&&D()})};N?N(_,W,ne):ne()}else r(_,d,m)},_e=(a,d,m,v=!1,b=!1)=>{const{type:_,props:C,ref:E,children:S,dynamicChildren:x,shapeFlag:L,patchFlag:P,dirs:N,cacheIndex:D,memo:W}=a;if(P===-2&&(b=!1),E!=null&&(pt(),on(E,null,m,a,!0),gt()),D!=null&&(d.renderCache[D]=void 0),L&256){d.ctx.deactivate(a);return}const ne=L&1&&N,te=!Ht(a);let pe;if(te&&(pe=C&&C.onVnodeBeforeUnmount)&&tt(pe,d,a),L&6)Pe(a.component,m,v);else{if(L&128){a.suspense.unmount(m,v);return}ne&&Et(a,null,d,"beforeUnmount"),L&64?a.type.remove(a,d,m,j,v):x&&!x.hasOnce&&(_!==fe||P>0&&P&64)?ve(x,d,m,!1,!0):(_===fe&&P&384||!b&&L&16)&&ve(S,d,m),v&&ue(a)}const be=W!=null&&D==null;(te&&(pe=C&&C.onVnodeUnmounted)||ne||be)&&Ne(()=>{pe&&tt(pe,d,a),ne&&Et(a,null,d,"unmounted"),be&&(a.el=null)},m)},ue=a=>{const{type:d,el:m,anchor:v,transition:b}=a;if(d===fe){oe(m,v);return}if(d===ir){$(a);return}const _=()=>{s(m),b&&!b.persisted&&b.afterLeave&&b.afterLeave()};if(a.shapeFlag&1&&b&&!b.persisted){const{leave:C,delayLeave:E}=b,S=()=>C(m,_);E?E(a.el,_,S):S()}else _()},oe=(a,d)=>{let m;for(;a!==d;)m=p(a),s(a),a=m;s(d)},Pe=(a,d,m)=>{const{bum:v,scope:b,job:_,subTree:C,um:E,m:S,a:x}=a;vs(S),vs(x),v&&Sn(v),b.stop(),_&&(_.flags|=8,_e(C,a,d,m)),E&&Ne(E,d),Ne(()=>{a.isUnmounted=!0},d)},ve=(a,d,m,v=!1,b=!1,_=0)=>{for(let C=_;C{if(a.shapeFlag&6)return y(a.component.subTree);if(a.shapeFlag&128)return a.suspense.next();const d=p(a.anchor||a.el),m=d&&d[Ll];return m?p(m):d};let T=!1;const A=(a,d,m)=>{let v;a==null?d._vnode&&(_e(d._vnode,null,null,!0),v=d._vnode.component):w(d._vnode||null,a,d,null,null,null,m),d._vnode=a,T||(T=!0,ls(v),Uo(),T=!1)},j={p:w,um:_e,m:Le,r:ue,mt:St,mc:He,pc:z,pbc:Oe,n:y,o:e};return{render:A,hydrate:void 0,createApp:lc(A)}}function or({type:e,props:t},n){return n==="svg"&&e==="foreignObject"||n==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:n}function Rt({effect:e,job:t},n){n?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function Sc(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function di(e,t,n=!1){const r=e.children,s=t.children;if(F(r)&&F(s))for(let o=0;o>1,e[n[l]]0&&(t[r]=n[o-1]),n[o]=r)}}for(o=n.length,i=n[o-1];o-- >0;)n[o]=i,i=t[i];return n}function hi(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:hi(t)}function vs(e){if(e)for(let t=0;te.__isSuspense;function Rc(e,t){t&&t.pendingBranch?F(e)?t.effects.push(...e):t.effects.push(e):Ol(e)}const fe=Symbol.for("v-fgt"),Wn=Symbol.for("v-txt"),yt=Symbol.for("v-cmt"),ir=Symbol.for("v-stc"),cn=[];let Fe=null;function V(e=!1){cn.push(Fe=e?null:[])}function Cc(){cn.pop(),Fe=cn[cn.length-1]||null}let hn=1;function $n(e,t=!1){hn+=e,e<0&&Fe&&t&&(Fe.hasOnce=!0)}function mi(e){return e.dynamicChildren=hn>0?Fe||Lt:null,Cc(),hn>0&&Fe&&Fe.push(e),e}function le(e,t,n,r,s,o){return mi(H(e,t,n,r,s,o,!0))}function Qe(e,t,n,r,s){return mi(ge(e,t,n,r,s,!0))}function pn(e){return e?e.__v_isVNode===!0:!1}function Jt(e,t){return e.type===t.type&&e.key===t.key}const yi=({key:e})=>e??null,Rn=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?ae(e)||ke(e)||U(e)?{i:Se,r:e,k:t,f:!!n}:e:null);function H(e,t=null,n=null,r=0,s=null,o=e===fe?0:1,i=!1,l=!1){const c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&yi(t),ref:t&&Rn(t),scopeId:Vo,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:o,patchFlag:r,dynamicProps:s,dynamicChildren:null,appContext:null,ctx:Se};return l?(Kr(c,n),o&128&&e.normalize(c)):n&&(c.shapeFlag|=ae(n)?8:16),hn>0&&!i&&Fe&&(c.patchFlag>0||o&6)&&c.patchFlag!==32&&Fe.push(c),c}const ge=kc;function kc(e,t=null,n=null,r=0,s=null,o=!1){if((!e||e===Jo)&&(e=yt),pn(e)){const l=Vt(e,t,!0);return n&&Kr(l,n),hn>0&&!o&&Fe&&(l.shapeFlag&6?Fe[Fe.indexOf(e)]=l:Fe.push(l)),l.patchFlag=-2,l}if(Fc(e)&&(e=e.__vccOpts),t){t=Ac(t);let{class:l,style:c}=t;l&&!ae(l)&&(t.class=wt(l)),Z(c)&&(Ur(c)&&!F(c)&&(c=Ee({},c)),t.style=Or(c))}const i=ae(e)?1:gi(e)?128:Dl(e)?64:Z(e)?4:U(e)?2:0;return H(e,t,n,r,s,i,o,!0)}function Ac(e){return e?Ur(e)||ii(e)?Ee({},e):e:null}function Vt(e,t,n=!1,r=!1){const{props:s,ref:o,patchFlag:i,children:l,transition:c}=e,f=t?Pc(s||{},t):s,u={__v_isVNode:!0,__v_skip:!0,type:e.type,props:f,key:f&&yi(f),ref:t&&t.ref?n&&o?F(o)?o.concat(Rn(t)):[o,Rn(t)]:Rn(t):o,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:l,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==fe?i===-1?16:i|16:i,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:c,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&Vt(e.ssContent),ssFallback:e.ssFallback&&Vt(e.ssFallback),placeholder:e.placeholder,el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return c&&r&&Vr(u,c.clone(u)),u}function _i(e=" ",t=0){return ge(Wn,null,e,t)}function jt(e="",t=!1){return t?(V(),Qe(yt,null,e)):ge(yt,null,e)}function st(e){return e==null||typeof e=="boolean"?ge(yt):F(e)?ge(fe,null,e.slice()):pn(e)?ft(e):ge(Wn,null,String(e))}function ft(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:Vt(e)}function Kr(e,t){let n=0;const{shapeFlag:r}=e;if(t==null)t=null;else if(F(t))n=16;else if(typeof t=="object")if(r&65){const s=t.default;s&&(s._c&&(s._d=!1),Kr(e,s()),s._c&&(s._d=!0));return}else{n=32;const s=t._;!s&&!ii(t)?t._ctx=Se:s===3&&Se&&(Se.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else U(t)?(t={default:t,_ctx:Se},n=32):(t=String(t),r&64?(n=16,t=[_i(t)]):n=8);e.children=t,e.shapeFlag|=n}function Pc(...e){const t={};for(let n=0;nCe||Se;let On,xr;{const e=Hn(),t=(n,r)=>{let s;return(s=e[n])||(s=e[n]=[]),s.push(r),o=>{s.length>1?s.forEach(i=>i(o)):s[0](o)}};On=t("__VUE_INSTANCE_SETTERS__",n=>Ce=n),xr=t("__VUE_SSR_SETTERS__",n=>gn=n)}const vn=e=>{const t=Ce;return On(e),e.scope.on(),()=>{e.scope.off(),On(t)}},bs=()=>{Ce&&Ce.scope.off(),On(null)};function vi(e){return e.vnode.shapeFlag&4}let gn=!1;function jc(e,t=!1,n=!1){t&&xr(t);const{props:r,children:s}=e.vnode,o=vi(e);gc(e,r,o,t),vc(e,s,n||t);const i=o?Nc(e,t):void 0;return t&&xr(!1),i}function Nc(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,ec);const{setup:r}=n;if(r){pt();const s=e.setupContext=r.length>1?Lc(e):null,o=vn(e),i=_n(r,e,0,[e.props,s]),l=po(i);if(gt(),o(),(l||e.sp)&&!Ht(e)&&Ko(e),l){if(i.then(bs,bs),t)return i.then(c=>{xs(e,c)}).catch(c=>{Vn(c,e,0)});e.asyncDep=i}else xs(e,i)}else bi(e)}function xs(e,t,n){U(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:Z(t)&&(e.setupState=Lo(t)),bi(e)}function bi(e,t,n){const r=e.type;e.render||(e.render=r.render||it);{const s=vn(e);pt();try{tc(e)}finally{gt(),s()}}}const Ic={get(e,t){return Re(e,"get",""),e[t]}};function Lc(e){const t=n=>{e.exposed=n||{}};return{attrs:new Proxy(e.attrs,Ic),slots:e.slots,emit:e.emit,expose:t}}function Gn(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(Lo(Sl(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in ln)return ln[n](e)},has(t,n){return n in t||n in ln}})):e.proxy}function Dc(e,t=!0){return U(e)?e.displayName||e.name:e.name||t&&e.__name}function Fc(e){return U(e)&&"__vccOpts"in e}const ce=(e,t)=>Al(e,t,gn);function mn(e,t,n){try{$n(-1);const r=arguments.length;return r===2?Z(t)&&!F(t)?pn(t)?ge(e,null,[t]):ge(e,t):ge(e,null,t):(r>3?n=Array.prototype.slice.call(arguments,2):r===3&&pn(n)&&(n=[n]),ge(e,t,n))}finally{$n(1)}}const Hc="3.5.35";let wr;const ws=typeof window<"u"&&window.trustedTypes;if(ws)try{wr=ws.createPolicy("vue",{createHTML:e=>e})}catch{}const xi=wr?e=>wr.createHTML(e):e=>e,Uc="http://www.w3.org/2000/svg",qc="http://www.w3.org/1998/Math/MathML",ut=typeof document<"u"?document:null,Ss=ut&&ut.createElement("template"),Vc={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{const s=t==="svg"?ut.createElementNS(Uc,e):t==="mathml"?ut.createElementNS(qc,e):n?ut.createElement(e,{is:n}):ut.createElement(e);return e==="select"&&r&&r.multiple!=null&&s.setAttribute("multiple",r.multiple),s},createText:e=>ut.createTextNode(e),createComment:e=>ut.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>ut.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,r,s,o){const i=n?n.previousSibling:t.lastChild;if(s&&(s===o||s.nextSibling))for(;t.insertBefore(s.cloneNode(!0),n),!(s===o||!(s=s.nextSibling)););else{Ss.innerHTML=xi(r==="svg"?`${e}`:r==="mathml"?`${e}`:e);const l=Ss.content;if(r==="svg"||r==="mathml"){const c=l.firstChild;for(;c.firstChild;)l.appendChild(c.firstChild);l.removeChild(c)}t.insertBefore(l,n)}return[i?i.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},zc=Symbol("_vtc");function Bc(e,t,n){const r=e[zc];r&&(t=(t?[t,...r]:[...r]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const Es=Symbol("_vod"),Kc=Symbol("_vsh"),Wc=Symbol(""),Gc=/(?:^|;)\s*display\s*:/;function Qc(e,t,n){const r=e.style,s=ae(n);let o=!1;if(n&&!s){if(t)if(ae(t))for(const i of t.split(";")){const l=i.slice(0,i.indexOf(":")).trim();n[l]==null&&Zt(r,l,"")}else for(const i in t)n[i]==null&&Zt(r,i,"");for(const i in n){i==="display"&&(o=!0);const l=n[i];l!=null?Yc(e,i,!ae(t)&&t?t[i]:void 0,l)||Zt(r,i,l):Zt(r,i,"")}}else if(s){if(t!==n){const i=r[Wc];i&&(n+=";"+i),r.cssText=n,o=Gc.test(n)}}else t&&e.removeAttribute("style");Es in e&&(e[Es]=o?r.display:"",e[Kc]&&(r.display="none"))}const Rs=/\s*!important$/;function Zt(e,t,n){if(F(n))n.forEach(r=>Zt(e,t,r));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const r=Jc(e,t);Rs.test(n)?e.setProperty(Tt(r),n.replace(Rs,""),"important"):e[r]=n}}const Cs=["Webkit","Moz","ms"],lr={};function Jc(e,t){const n=lr[t];if(n)return n;let r=$e(t);if(r!=="filter"&&r in e)return lr[t]=r;r=Fn(r);for(let s=0;scr||(ta.then(()=>cr=0),cr=Date.now());function ra(e,t){const n=r=>{if(!r._vts)r._vts=Date.now();else if(r._vts<=n.attached)return;const s=n.value;if(F(s)){const o=r.stopImmediatePropagation;r.stopImmediatePropagation=()=>{o.call(r),r._stopped=!0};const i=s.slice(),l=[r];for(let c=0;ce.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,sa=(e,t,n,r,s,o)=>{const i=s==="svg";t==="class"?Bc(e,r,i):t==="style"?Qc(e,n,r):In(t)?Ln(t)||Zc(e,t,n,r,o):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):oa(e,t,r,i))?(Ps(e,t,r),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&As(e,t,r,i,o,t!=="value")):e._isVueCE&&(ia(e,t)||e._def.__asyncLoader&&(/[A-Z]/.test(t)||!ae(r)))?Ps(e,$e(t),r,o,t):(t==="true-value"?e._trueValue=r:t==="false-value"&&(e._falseValue=r),As(e,t,r,i))};function oa(e,t,n,r){if(r)return!!(t==="innerHTML"||t==="textContent"||t in e&&$s(t)&&U(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="autocorrect"||t==="sandbox"&&e.tagName==="IFRAME"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const s=e.tagName;if(s==="IMG"||s==="VIDEO"||s==="CANVAS"||s==="SOURCE")return!1}return $s(t)&&ae(n)?!1:t in e}function ia(e,t){const n=e._def.props;if(!n)return!1;const r=$e(t);return Array.isArray(n)?n.some(s=>$e(s)===r):Object.keys(n).some(s=>$e(s)===r)}const Os=e=>{const t=e.props["onUpdate:modelValue"]||!1;return F(t)?n=>Sn(t,n):t};function la(e){e.target.composing=!0}function js(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const ar=Symbol("_assign");function Ns(e,t,n){return t&&(e=e.trim()),n&&(e=$r(e)),e}const Is={created(e,{modifiers:{lazy:t,trim:n,number:r}},s){e[ar]=Os(s);const o=r||s.props&&s.props.type==="number";Nt(e,t?"change":"input",i=>{i.target.composing||e[ar](Ns(e.value,n,o))}),(n||o)&&Nt(e,"change",()=>{e.value=Ns(e.value,n,o)}),t||(Nt(e,"compositionstart",la),Nt(e,"compositionend",js),Nt(e,"change",js))},mounted(e,{value:t}){e.value=t??""},beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:r,trim:s,number:o}},i){if(e[ar]=Os(i),e.composing)return;const l=(o||e.type==="number")&&!/^0\d/.test(e.value)?$r(e.value):e.value,c=t??"";if(l===c)return;const f=e.getRootNode();(f instanceof Document||f instanceof ShadowRoot)&&f.activeElement===e&&e.type!=="range"&&(r&&t===n||s&&e.value.trim()===c)||(e.value=c)}},ca=["ctrl","shift","alt","meta"],aa={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>ca.some(n=>e[`${n}Key`]&&!t.includes(n))},ua=(e,t)=>{if(!e)return e;const n=e._withMods||(e._withMods={}),r=t.join(".");return n[r]||(n[r]=((s,...o)=>{for(let i=0;i{const t=da().createApp(...e),{mount:n}=t;return t.mount=r=>{const s=ga(r);if(!s)return;const o=t._component;!U(o)&&!o.render&&!o.template&&(o.template=s.innerHTML),s.nodeType===1&&(s.textContent="");const i=n(s,!1,pa(s));return s instanceof Element&&(s.removeAttribute("v-cloak"),s.setAttribute("data-v-app","")),i},t});function pa(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function ga(e){return ae(e)?document.querySelector(e):e}const It=typeof document<"u";function wi(e){return typeof e=="object"||"displayName"in e||"props"in e||"__vccOpts"in e}function ma(e){return e.__esModule||e[Symbol.toStringTag]==="Module"||e.default&&wi(e.default)}const Q=Object.assign;function ur(e,t){const n={};for(const r in t){const s=t[r];n[r]=Je(s)?s.map(e):e(s)}return n}const an=()=>{},Je=Array.isArray;function Ds(e,t){const n={};for(const r in e)n[r]=r in t?t[r]:e[r];return n}const Si=Symbol("");function zt(e,t){return Q(new Error,{type:e,[Si]:!0},t)}function at(e,t){return e instanceof Error&&Si in e&&(t==null||!!(e.type&t))}const ya=Symbol(""),Fs=Symbol(""),Qn=Symbol(""),Wr=Symbol(""),Sr=Symbol("");function _a(){return ze(Qn)}function Wt(e){return ze(Wr)}const Ei=/#/g,va=/&/g,ba=/\//g,xa=/=/g,wa=/\?/g,Ri=/\+/g,Sa=/%5B/g,Ea=/%5D/g,Ci=/%5E/g,Ra=/%60/g,ki=/%7B/g,Ca=/%7C/g,Ai=/%7D/g,ka=/%20/g;function Gr(e){return e==null?"":encodeURI(""+e).replace(Ca,"|").replace(Sa,"[").replace(Ea,"]")}function Aa(e){return Gr(e).replace(ki,"{").replace(Ai,"}").replace(Ci,"^")}function Er(e){return Gr(e).replace(Ri,"%2B").replace(ka,"+").replace(Ei,"%23").replace(va,"%26").replace(Ra,"`").replace(ki,"{").replace(Ai,"}").replace(Ci,"^")}function Pa(e){return Er(e).replace(xa,"%3D")}function Ta(e){return Gr(e).replace(Ei,"%23").replace(wa,"%3F")}function Ma(e){return Ta(e).replace(ba,"%2F")}function jn(e){if(e==null)return null;try{return decodeURIComponent(""+e)}catch{}return""+e}const $a=/\/$/,Oa=e=>e.replace($a,"");function fr(e,t,n="/"){let r,s={},o="",i="";const l=t.indexOf("#");let c=t.indexOf("?");return c=l>=0&&c>l?-1:c,c>=0&&(r=t.slice(0,c),o=t.slice(c,l>0?l:t.length),s=e(o.slice(1))),l>=0&&(r=r||t.slice(0,l),i=t.slice(l,t.length)),r=La(r??t,n),{fullPath:r+o+i,path:r,query:s,hash:jn(i)}}function ja(e,t){const n=t.query?e(t.query):"";return t.path+(n&&"?")+n+(t.hash||"")}function Hs(e,t){return!t||!e.toLowerCase().startsWith(t.toLowerCase())?e:e.slice(t.length)||"/"}function Na(e,t,n){const r=t.matched.length-1,s=n.matched.length-1;return r>-1&&r===s&&Bt(t.matched[r],n.matched[s])&&Pi(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}function Bt(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}function Pi(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(var n in e)if(!Ia(e[n],t[n]))return!1;return!0}function Ia(e,t){return Je(e)?Us(e,t):Je(t)?Us(t,e):(e&&e.valueOf())===(t&&t.valueOf())}function Us(e,t){return Je(t)?e.length===t.length&&e.every((n,r)=>n===t[r]):e.length===1&&e[0]===t}function La(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t.split("/"),r=e.split("/"),s=r[r.length-1];(s===".."||s===".")&&r.push("");let o=n.length-1,i,l;for(i=0;i1&&o--;else break;return n.slice(0,o).join("/")+"/"+r.slice(i).join("/")}const vt={path:"/",name:void 0,params:{},query:{},hash:"",fullPath:"/",matched:[],meta:{},redirectedFrom:void 0};function Da(e){if(!e)if(It){const t=document.querySelector("base");e=t&&t.getAttribute("href")||"/",e=e.replace(/^\w+:\/\/[^/]+/,"")}else e="/";return e[0]!=="/"&&e[0]!=="#"&&(e="/"+e),Oa(e)}const Fa=/^[^#]+#/;function Ha(e,t){return e.replace(Fa,"#")+t}function Ua(e,t){const n=document.documentElement.getBoundingClientRect(),r=e.getBoundingClientRect();return{behavior:t.behavior,left:r.left-n.left-(t.left||0),top:r.top-n.top-(t.top||0)}}const Jn=()=>({left:window.scrollX,top:window.scrollY});function qa(e){let t;if("el"in e){const n=e.el,r=typeof n=="string"&&n.startsWith("#"),s=typeof n=="string"?r?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!s)return;t=Ua(s,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(t.left!=null?t.left:window.scrollX,t.top!=null?t.top:window.scrollY)}function qs(e,t){return(history.state?history.state.position-t:-1)+e}const Rr=new Map;function Va(e,t){Rr.set(e,t)}function za(e){const t=Rr.get(e);return Rr.delete(e),t}function Ba(e){return typeof e=="string"||e&&typeof e=="object"}function Ti(e){return typeof e=="string"||typeof e=="symbol"}function Ka(e){const t={};if(e===""||e==="?")return t;const n=(e[0]==="?"?e.slice(1):e).split("&");for(let r=0;rs&&Er(s)):[r&&Er(r)]).forEach(s=>{s!==void 0&&(t+=(t.length?"&":"")+n,s!=null&&(t+="="+s))})}return t}function Wa(e){const t={};for(const n in e){const r=e[n];r!==void 0&&(t[n]=Je(r)?r.map(s=>s==null?null:""+s):r==null?r:""+r)}return t}function Yt(){let e=[];function t(r){return e.push(r),()=>{const s=e.indexOf(r);s>-1&&e.splice(s,1)}}function n(){e=[]}return{add:t,list:()=>e.slice(),reset:n}}function xt(e,t,n,r,s,o=i=>i()){const i=r&&(r.enterCallbacks[s]=r.enterCallbacks[s]||[]);return()=>new Promise((l,c)=>{const f=p=>{p===!1?c(zt(4,{from:n,to:t})):p instanceof Error?c(p):Ba(p)?c(zt(2,{from:t,to:p})):(i&&r.enterCallbacks[s]===i&&typeof p=="function"&&i.push(p),l())},u=o(()=>e.call(r&&r.instances[s],t,n,f));let h=Promise.resolve(u);e.length<3&&(h=h.then(f)),h.catch(p=>c(p))})}function dr(e,t,n,r,s=o=>o()){const o=[];for(const i of e)for(const l in i.components){let c=i.components[l];if(!(t!=="beforeRouteEnter"&&!i.instances[l]))if(wi(c)){const f=(c.__vccOpts||c)[t];f&&o.push(xt(f,n,r,i,l,s))}else{let f=c();o.push(()=>f.then(u=>{if(!u)throw new Error(`Couldn't resolve component "${l}" at "${i.path}"`);const h=ma(u)?u.default:u;i.mods[l]=u,i.components[l]=h;const p=(h.__vccOpts||h)[t];return p&&xt(p,n,r,i,l,s)()}))}}return o}function Ga(e,t){const n=[],r=[],s=[],o=Math.max(t.matched.length,e.matched.length);for(let i=0;iBt(f,l))?r.push(l):n.push(l));const c=e.matched[i];c&&(t.matched.find(f=>Bt(f,c))||s.push(c))}return[n,r,s]}let Qa=()=>location.protocol+"//"+location.host;function Mi(e,t){const{pathname:n,search:r,hash:s}=t,o=e.indexOf("#");if(o>-1){let i=s.includes(e.slice(o))?e.slice(o).length:1,l=s.slice(i);return l[0]!=="/"&&(l="/"+l),Hs(l,"")}return Hs(n,e)+r+s}function Ja(e,t,n,r){let s=[],o=[],i=null;const l=({state:p})=>{const g=Mi(e,location),R=n.value,w=t.value;let I=0;if(p){if(n.value=g,t.value=p,i&&i===R){i=null;return}I=w?p.position-w.position:0}else r(g);s.forEach(O=>{O(n.value,R,{delta:I,type:"pop",direction:I?I>0?"forward":"back":""})})};function c(){i=n.value}function f(p){s.push(p);const g=()=>{const R=s.indexOf(p);R>-1&&s.splice(R,1)};return o.push(g),g}function u(){if(document.visibilityState==="hidden"){const{history:p}=window;if(!p.state)return;p.replaceState(Q({},p.state,{scroll:Jn()}),"")}}function h(){for(const p of o)p();o=[],window.removeEventListener("popstate",l),window.removeEventListener("pagehide",u),document.removeEventListener("visibilitychange",u)}return window.addEventListener("popstate",l),window.addEventListener("pagehide",u),document.addEventListener("visibilitychange",u),{pauseListeners:c,listen:f,destroy:h}}function zs(e,t,n,r=!1,s=!1){return{back:e,current:t,forward:n,replaced:r,position:window.history.length,scroll:s?Jn():null}}function Ya(e){const{history:t,location:n}=window,r={value:Mi(e,n)},s={value:t.state};s.value||o(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function o(c,f,u){const h=e.indexOf("#"),p=h>-1?(n.host&&document.querySelector("base")?e:e.slice(h))+c:Qa()+e+c;try{t[u?"replaceState":"pushState"](f,"",p),s.value=f}catch(g){console.error(g),n[u?"replace":"assign"](p)}}function i(c,f){o(c,Q({},t.state,zs(s.value.back,c,s.value.forward,!0),f,{position:s.value.position}),!0),r.value=c}function l(c,f){const u=Q({},s.value,t.state,{forward:c,scroll:Jn()});o(u.current,u,!0),o(c,Q({},zs(r.value,c,null),{position:u.position+1},f),!1),r.value=c}return{location:r,state:s,push:l,replace:i}}function Xa(e){e=Da(e);const t=Ya(e),n=Ja(e,t.state,t.location,t.replace);function r(o,i=!0){i||n.pauseListeners(),history.go(o)}const s=Q({location:"",base:e,go:r,createHref:Ha.bind(null,e)},t,n);return Object.defineProperty(s,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(s,"state",{enumerable:!0,get:()=>t.state.value}),s}const Za={type:0,value:""},eu=/[a-zA-Z0-9_]/;function tu(e){if(!e)return[[]];if(e==="/")return[[Za]];if(!e.startsWith("/"))throw new Error(`Invalid path "${e}"`);function t(g){throw new Error(`ERR (${n})/"${f}": ${g}`)}let n=0,r=n;const s=[];let o;function i(){o&&s.push(o),o=[]}let l=0,c,f="",u="";function h(){f&&(n===0?o.push({type:0,value:f}):n===1||n===2||n===3?(o.length>1&&(c==="*"||c==="+")&&t(`A repeatable param (${f}) must be alone in its segment. eg: '/:ids+.`),o.push({type:1,value:f,regexp:u,repeatable:c==="*"||c==="+",optional:c==="*"||c==="?"})):t("Invalid state to consume buffer"),f="")}function p(){f+=c}for(;lt.length?t.length===1&&t[0]===80?1:-1:0}function $i(e,t){let n=0;const r=e.score,s=t.score;for(;n0&&t[t.length-1]<0}const iu={strict:!1,end:!0,sensitive:!1};function lu(e,t,n){const r=su(tu(e.path),n),s=Q(r,{record:e,parent:t,children:[],alias:[]});return t&&!s.record.aliasOf==!t.record.aliasOf&&t.children.push(s),s}function cu(e,t){const n=[],r=new Map;t=Ds(iu,t);function s(h){return r.get(h)}function o(h,p,g){const R=!g,w=Gs(h);w.aliasOf=g&&g.record;const I=Ds(t,h),O=[w];if("alias"in h){const $=typeof h.alias=="string"?[h.alias]:h.alias;for(const G of $)O.push(Gs(Q({},w,{components:g?g.record.components:w.components,path:G,aliasOf:g?g.record:w})))}let k,M;for(const $ of O){const{path:G}=$;if(p&&G[0]!=="/"){const me=p.record.path,se=me[me.length-1]==="/"?"":"/";$.path=p.record.path+(G&&se+G)}if(k=lu($,p,I),g?g.alias.push(k):(M=M||k,M!==k&&M.alias.push(k),R&&h.name&&!Qs(k)&&i(h.name)),Oi(k)&&c(k),w.children){const me=w.children;for(let se=0;se{i(M)}:an}function i(h){if(Ti(h)){const p=r.get(h);p&&(r.delete(h),n.splice(n.indexOf(p),1),p.children.forEach(i),p.alias.forEach(i))}else{const p=n.indexOf(h);p>-1&&(n.splice(p,1),h.record.name&&r.delete(h.record.name),h.children.forEach(i),h.alias.forEach(i))}}function l(){return n}function c(h){const p=fu(h,n);n.splice(p,0,h),h.record.name&&!Qs(h)&&r.set(h.record.name,h)}function f(h,p){let g,R={},w,I;if("name"in h&&h.name){if(g=r.get(h.name),!g)throw zt(1,{location:h});I=g.record.name,R=Q(Ws(p.params,g.keys.filter(M=>!M.optional).concat(g.parent?g.parent.keys.filter(M=>M.optional):[]).map(M=>M.name)),h.params&&Ws(h.params,g.keys.map(M=>M.name))),w=g.stringify(R)}else if(h.path!=null)w=h.path,g=n.find(M=>M.re.test(w)),g&&(R=g.parse(w),I=g.record.name,g.keys.forEach(M=>{M.optional&&!R[M.name]&&delete R[M.name]}));else{if(g=p.name?r.get(p.name):n.find(M=>M.re.test(p.path)),!g)throw zt(1,{location:h,currentLocation:p});I=g.record.name,R=Q({},p.params,h.params),w=g.stringify(R)}const O=[];let k=g;for(;k;)O.unshift(k.record),k=k.parent;return{name:I,path:w,params:R,matched:O,meta:uu(O)}}e.forEach(h=>o(h));function u(){n.length=0,r.clear()}return{addRoute:o,resolve:f,removeRoute:i,clearRoutes:u,getRoutes:l,getRecordMatcher:s}}function Ws(e,t){const n={};for(const r of t)r in e&&(n[r]=e[r]);return n}function Gs(e){const t={path:e.path,redirect:e.redirect,name:e.name,meta:e.meta||{},aliasOf:e.aliasOf,beforeEnter:e.beforeEnter,props:au(e),children:e.children||[],instances:{},leaveGuards:new Set,updateGuards:new Set,enterCallbacks:{},components:"components"in e?e.components||null:e.component&&{default:e.component}};return Object.defineProperty(t,"mods",{value:{}}),t}function au(e){const t={},n=e.props||!1;if("component"in e)t.default=n;else for(const r in e.components)t[r]=typeof n=="object"?n[r]:n;return t}function Qs(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function uu(e){return e.reduce((t,n)=>Q(t,n.meta),{})}function fu(e,t){let n=0,r=t.length;for(;n!==r;){const o=n+r>>1;$i(e,t[o])<0?r=o:n=o+1}const s=du(e);return s&&(r=t.lastIndexOf(s,r-1)),r}function du(e){let t=e;for(;t=t.parent;)if(Oi(t)&&$i(e,t)===0)return t}function Oi({record:e}){return!!(e.name||e.components&&Object.keys(e.components).length||e.redirect)}function Js(e){const t=ze(Qn),n=ze(Wr),r=ce(()=>{const c=X(e.to);return t.resolve(c)}),s=ce(()=>{const{matched:c}=r.value,{length:f}=c,u=c[f-1],h=n.matched;if(!u||!h.length)return-1;const p=h.findIndex(Bt.bind(null,u));if(p>-1)return p;const g=Ys(c[f-2]);return f>1&&Ys(u)===g&&h[h.length-1].path!==g?h.findIndex(Bt.bind(null,c[f-2])):p}),o=ce(()=>s.value>-1&&gu(n.params,r.value.params)),i=ce(()=>s.value>-1&&s.value===n.matched.length-1&&Pi(n.params,r.value.params));function l(c={}){if(pu(c)){const f=t[X(e.replace)?"replace":"push"](X(e.to)).catch(an);return e.viewTransition&&typeof document<"u"&&"startViewTransition"in document&&document.startViewTransition(()=>f),f}return Promise.resolve()}return{route:r,href:ce(()=>r.value.href),isActive:o,isExactActive:i,navigate:l}}function hu(e){return e.length===1?e[0]:e}const Qr=lt({name:"RouterLink",compatConfig:{MODE:3},props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:"page"},viewTransition:Boolean},useLink:Js,setup(e,{slots:t}){const n=qn(Js(e)),{options:r}=ze(Qn),s=ce(()=>({[Xs(e.activeClass,r.linkActiveClass,"router-link-active")]:n.isActive,[Xs(e.exactActiveClass,r.linkExactActiveClass,"router-link-exact-active")]:n.isExactActive}));return()=>{const o=t.default&&hu(t.default(n));return e.custom?o:mn("a",{"aria-current":n.isExactActive?e.ariaCurrentValue:null,href:n.href,onClick:n.navigate,class:s.value},o)}}});function pu(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget&&e.currentTarget.getAttribute){const t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}function gu(e,t){for(const n in t){const r=t[n],s=e[n];if(typeof r=="string"){if(r!==s)return!1}else if(!Je(s)||s.length!==r.length||r.some((o,i)=>o.valueOf()!==s[i].valueOf()))return!1}return!0}function Ys(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}const Xs=(e,t,n)=>e??t??n,mu=lt({name:"RouterView",inheritAttrs:!1,props:{name:{type:String,default:"default"},route:Object},compatConfig:{MODE:3},setup(e,{attrs:t,slots:n}){const r=ze(Sr),s=ce(()=>e.route||r.value),o=ze(Fs,0),i=ce(()=>{let f=X(o);const{matched:u}=s.value;let h;for(;(h=u[f])&&!h.components;)f++;return f}),l=ce(()=>s.value.matched[i.value]);En(Fs,ce(()=>i.value+1)),En(ya,l),En(Sr,s);const c=Ie();return sn(()=>[c.value,l.value,e.name],([f,u,h],[p,g,R])=>{u&&(u.instances[h]=f,g&&g!==u&&f&&f===p&&(u.leaveGuards.size||(u.leaveGuards=g.leaveGuards),u.updateGuards.size||(u.updateGuards=g.updateGuards))),f&&u&&(!g||!Bt(u,g)||!p)&&(u.enterCallbacks[h]||[]).forEach(w=>w(f))},{flush:"post"}),()=>{const f=s.value,u=e.name,h=l.value,p=h&&h.components[u];if(!p)return Zs(n.default,{Component:p,route:f});const g=h.props[u],R=g?g===!0?f.params:typeof g=="function"?g(f):g:null,I=mn(p,Q({},R,t,{onVnodeUnmounted:O=>{O.component.isUnmounted&&(h.instances[u]=null)},ref:c}));return Zs(n.default,{Component:I,route:f})||I}}});function Zs(e,t){if(!e)return null;const n=e(t);return n.length===1?n[0]:n}const Cr=mu;function yu(e){const t=cu(e.routes,e),n=e.parseQuery||Ka,r=e.stringifyQuery||Vs,s=e.history,o=Yt(),i=Yt(),l=Yt(),c=El(vt);let f=vt;It&&e.scrollBehavior&&"scrollRestoration"in history&&(history.scrollRestoration="manual");const u=ur.bind(null,y=>""+y),h=ur.bind(null,Ma),p=ur.bind(null,jn);function g(y,T){let A,j;return Ti(y)?(A=t.getRecordMatcher(y),j=T):j=y,t.addRoute(j,A)}function R(y){const T=t.getRecordMatcher(y);T&&t.removeRoute(T)}function w(){return t.getRoutes().map(y=>y.record)}function I(y){return!!t.getRecordMatcher(y)}function O(y,T){if(T=Q({},T||c.value),typeof y=="string"){const m=fr(n,y,T.path),v=t.resolve({path:m.path},T),b=s.createHref(m.fullPath);return Q(m,v,{params:p(v.params),redirectedFrom:void 0,href:b})}let A;if(y.path!=null)A=Q({},y,{path:fr(n,y.path,T.path).path});else{const m=Q({},y.params);for(const v in m)m[v]==null&&delete m[v];A=Q({},y,{params:h(m)}),T.params=h(T.params)}const j=t.resolve(A,T),B=y.hash||"";j.params=u(p(j.params));const a=ja(r,Q({},y,{hash:Aa(B),path:j.path})),d=s.createHref(a);return Q({fullPath:a,hash:B,query:r===Vs?Wa(y.query):y.query||{}},j,{redirectedFrom:void 0,href:d})}function k(y){return typeof y=="string"?fr(n,y,c.value.path):Q({},y)}function M(y,T){if(f!==y)return zt(8,{from:T,to:y})}function $(y){return se(y)}function G(y){return $(Q(k(y),{replace:!0}))}function me(y,T){const A=y.matched[y.matched.length-1];if(A&&A.redirect){const{redirect:j}=A;let B=typeof j=="function"?j(y,T):j;return typeof B=="string"&&(B=B.includes("?")||B.includes("#")?B=k(B):{path:B},B.params={}),Q({query:y.query,hash:y.hash,params:B.path!=null?{}:y.params},B)}}function se(y,T){const A=f=O(y),j=c.value,B=y.state,a=y.force,d=y.replace===!0,m=me(A,j);if(m)return se(Q(k(m),{state:typeof m=="object"?Q({},B,m.state):B,force:a,replace:d}),T||A);const v=A;v.redirectedFrom=T;let b;return!a&&Na(r,j,A)&&(b=zt(16,{to:v,from:j}),Le(j,j,!0,!1)),(b?Promise.resolve(b):Oe(v,j)).catch(_=>at(_)?at(_,2)?_:ee(_):z(_,v,j)).then(_=>{if(_){if(at(_,2))return se(Q({replace:d},k(_.to),{state:typeof _.to=="object"?Q({},B,_.to.state):B,force:a}),T||v)}else _=Ye(v,j,!0,d,B);return qe(v,j,_),_})}function He(y,T){const A=M(y,T);return A?Promise.reject(A):Promise.resolve()}function Ue(y){const T=oe.values().next().value;return T&&typeof T.runWithContext=="function"?T.runWithContext(y):y()}function Oe(y,T){let A;const[j,B,a]=Ga(y,T);A=dr(j.reverse(),"beforeRouteLeave",y,T);for(const m of j)m.leaveGuards.forEach(v=>{A.push(xt(v,y,T))});const d=He.bind(null,y,T);return A.push(d),ve(A).then(()=>{A=[];for(const m of o.list())A.push(xt(m,y,T));return A.push(d),ve(A)}).then(()=>{A=dr(B,"beforeRouteUpdate",y,T);for(const m of B)m.updateGuards.forEach(v=>{A.push(xt(v,y,T))});return A.push(d),ve(A)}).then(()=>{A=[];for(const m of a)if(m.beforeEnter)if(Je(m.beforeEnter))for(const v of m.beforeEnter)A.push(xt(v,y,T));else A.push(xt(m.beforeEnter,y,T));return A.push(d),ve(A)}).then(()=>(y.matched.forEach(m=>m.enterCallbacks={}),A=dr(a,"beforeRouteEnter",y,T,Ue),A.push(d),ve(A))).then(()=>{A=[];for(const m of i.list())A.push(xt(m,y,T));return A.push(d),ve(A)}).catch(m=>at(m,8)?m:Promise.reject(m))}function qe(y,T,A){l.list().forEach(j=>Ue(()=>j(y,T,A)))}function Ye(y,T,A,j,B){const a=M(y,T);if(a)return a;const d=T===vt,m=It?history.state:{};A&&(j||d?s.replace(y.fullPath,Q({scroll:d&&m&&m.scroll},B)):s.push(y.fullPath,B)),c.value=y,Le(y,T,A,d),ee()}let je;function St(){je||(je=s.listen((y,T,A)=>{if(!Pe.listening)return;const j=O(y),B=me(j,Pe.currentRoute.value);if(B){se(Q(B,{replace:!0,force:!0}),j).catch(an);return}f=j;const a=c.value;It&&Va(qs(a.fullPath,A.delta),Jn()),Oe(j,a).catch(d=>at(d,12)?d:at(d,2)?(se(Q(k(d.to),{force:!0}),j).then(m=>{at(m,20)&&!A.delta&&A.type==="pop"&&s.go(-1,!1)}).catch(an),Promise.reject()):(A.delta&&s.go(-A.delta,!1),z(d,j,a))).then(d=>{d=d||Ye(j,a,!1),d&&(A.delta&&!at(d,8)?s.go(-A.delta,!1):A.type==="pop"&&at(d,20)&&s.go(-1,!1)),qe(j,a,d)}).catch(an)}))}let Ae=Yt(),he=Yt(),K;function z(y,T,A){ee(y);const j=he.list();return j.length?j.forEach(B=>B(y,T,A)):console.error(y),Promise.reject(y)}function ye(){return K&&c.value!==vt?Promise.resolve():new Promise((y,T)=>{Ae.add([y,T])})}function ee(y){return K||(K=!y,St(),Ae.list().forEach(([T,A])=>y?A(y):T()),Ae.reset()),y}function Le(y,T,A,j){const{scrollBehavior:B}=e;if(!It||!B)return Promise.resolve();const a=!A&&za(qs(y.fullPath,0))||(j||!A)&&history.state&&history.state.scroll||null;return Fo().then(()=>B(y,T,a)).then(d=>d&&qa(d)).catch(d=>z(d,y,T))}const _e=y=>s.go(y);let ue;const oe=new Set,Pe={currentRoute:c,listening:!0,addRoute:g,removeRoute:R,clearRoutes:t.clearRoutes,hasRoute:I,getRoutes:w,resolve:O,options:e,push:$,replace:G,go:_e,back:()=>_e(-1),forward:()=>_e(1),beforeEach:o.add,beforeResolve:i.add,afterEach:l.add,onError:he.add,isReady:ye,install(y){y.component("RouterLink",Qr),y.component("RouterView",Cr),y.config.globalProperties.$router=Pe,Object.defineProperty(y.config.globalProperties,"$route",{enumerable:!0,get:()=>X(c)}),It&&!ue&&c.value===vt&&(ue=!0,$(s.location).catch(j=>{}));const T={};for(const j in vt)Object.defineProperty(T,j,{get:()=>c.value[j],enumerable:!0});y.provide(Qn,Pe),y.provide(Wr,No(T)),y.provide(Sr,c);const A=y.unmount;oe.add(y),y.unmount=function(){oe.delete(y),oe.size<1&&(f=vt,je&&je(),je=null,c.value=vt,ue=!1,K=!1),A()}}};function ve(y){return y.reduce((T,A)=>T.then(()=>Ue(A)),Promise.resolve())}return Pe}const eo=e=>e==="";const _u=(...e)=>e.filter((t,n,r)=>!!t&&t.trim()!==""&&r.indexOf(t)===n).join(" ").trim();const to=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase();const vu=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(t,n,r)=>r?r.toUpperCase():n.toLowerCase());const bu=e=>{const t=vu(e);return t.charAt(0).toUpperCase()+t.slice(1)};var $t={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":2,"stroke-linecap":"round","stroke-linejoin":"round"};const xu=Symbol("lucide-icons");function wu(){return ze(xu,{})}const Su=({name:e,iconNode:t,absoluteStrokeWidth:n,"absolute-stroke-width":r,strokeWidth:s,"stroke-width":o,size:i,color:l,...c},{slots:f})=>{const{size:u,color:h,strokeWidth:p=2,absoluteStrokeWidth:g=!1,class:R=""}=wu(),w=ce(()=>{const I=eo(n)||eo(r)||n===!0||r===!0||g===!0,O=s||o||p||$t["stroke-width"];return I?Number(O)*24/Number(i??u??$t.width):O});return mn("svg",{...$t,...c,width:i??u??$t.width,height:i??u??$t.height,stroke:l??h??$t.stroke,"stroke-width":w.value,class:_u("lucide",R,...e?[`lucide-${to(bu(e))}-icon`,`lucide-${to(e)}`]:["lucide-icon"])},[...t.map(I=>mn(...I)),...f.default?[f.default()]:[]])};const q=(e,t)=>(n,{slots:r,attrs:s})=>mn(Su,{...s,...n,iconNode:t,name:e},r.default?{default:r.default}:void 0);const Eu=[["rect",{width:"20",height:"5",x:"2",y:"3",rx:"1",key:"1wp1u1"}],["path",{d:"M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8",key:"1s80jp"}],["path",{d:"M10 12h4",key:"a56b0p"}]],Ru=q("archive",Eu);const Cu=[["path",{d:"M12 5v14",key:"s699le"}],["path",{d:"m19 12-7 7-7-7",key:"1idqje"}]],ku=q("arrow-down",Cu);const Au=[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]],Pu=q("arrow-left",Au);const Tu=[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"m12 5 7 7-7 7",key:"xquz4c"}]],Mu=q("arrow-right",Tu);const $u=[["path",{d:"M7 7h10v10",key:"1tivn9"}],["path",{d:"M7 17 17 7",key:"1vkiza"}]],Ou=q("arrow-up-right",$u);const ju=[["path",{d:"m5 12 7-7 7 7",key:"hav0vg"}],["path",{d:"M12 19V5",key:"x0mq9r"}]],Nu=q("arrow-up",ju);const Iu=[["rect",{width:"20",height:"16",x:"2",y:"4",rx:"2",key:"18n3k1"}],["circle",{cx:"8",cy:"10",r:"2",key:"1xl4ub"}],["path",{d:"M8 12h8",key:"1wcyev"}],["circle",{cx:"16",cy:"10",r:"2",key:"r14t7q"}],["path",{d:"m6 20 .7-2.9A1.4 1.4 0 0 1 8.1 16h7.8a1.4 1.4 0 0 1 1.4 1l.7 3",key:"l01ucn"}]],Lu=q("cassette-tape",Iu);const Du=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],Fu=q("check",Du);const Hu=[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]],Uu=q("chevron-down",Hu);const qu=[["path",{d:"m15 18-6-6 6-6",key:"1wnfg3"}]],Vu=q("chevron-left",qu);const zu=[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]],ji=q("chevron-right",zu);const Bu=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12",key:"1pkeuh"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16",key:"4dfq90"}]],Ni=q("circle-alert",Bu);const Ku=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],Wu=q("circle",Ku);const Gu=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M6 12c0-1.7.7-3.2 1.8-4.2",key:"oqkarx"}],["circle",{cx:"12",cy:"12",r:"2",key:"1c9p78"}],["path",{d:"M18 12c0 1.7-.7 3.2-1.8 4.2",key:"1eah9h"}]],Qu=q("disc-3",Gu);const Ju=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["circle",{cx:"12",cy:"12",r:"2",key:"1c9p78"}]],Yu=q("disc",Ju);const Xu=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2",key:"1m3agn"}],["circle",{cx:"9",cy:"9",r:"2",key:"af1f0g"}],["path",{d:"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21",key:"1xmnt7"}]],Zu=q("image",Xu);const ef=[["path",{d:"m16 6 4 14",key:"ji33uf"}],["path",{d:"M12 6v14",key:"1n7gus"}],["path",{d:"M8 8v12",key:"1gg7y9"}],["path",{d:"M4 4v16",key:"6qkkli"}]],tf=q("library",ef);const nf=[["path",{d:"M3 5h.01",key:"18ugdj"}],["path",{d:"M3 12h.01",key:"nlz23k"}],["path",{d:"M3 19h.01",key:"noohij"}],["path",{d:"M8 5h13",key:"1pao27"}],["path",{d:"M8 12h13",key:"1za7za"}],["path",{d:"M8 19h13",key:"m83p4d"}]],rf=q("list",nf);const sf=[["path",{d:"m16 17 5-5-5-5",key:"1bji2h"}],["path",{d:"M21 12H9",key:"dn1m92"}],["path",{d:"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4",key:"1uf3rs"}]],of=q("log-out",sf);const lf=[["path",{d:"m11 7.601-5.994 8.19a1 1 0 0 0 .1 1.298l.817.818a1 1 0 0 0 1.314.087L15.09 12",key:"80a601"}],["path",{d:"M16.5 21.174C15.5 20.5 14.372 20 13 20c-2.058 0-3.928 2.356-6 2-2.072-.356-2.775-3.369-1.5-4.5",key:"j0ngtp"}],["circle",{cx:"16",cy:"7",r:"5",key:"d08jfb"}]],cf=q("mic-vocal",lf);const af=[["path",{d:"M5 12h14",key:"1ays0h"}]],uf=q("minus",af);const ff=[["path",{d:"M12 22a1 1 0 0 1 0-20 10 9 0 0 1 10 9 5 5 0 0 1-5 5h-2.25a1.75 1.75 0 0 0-1.4 2.8l.3.4a1.75 1.75 0 0 1-1.4 2.8z",key:"e79jfc"}],["circle",{cx:"13.5",cy:"6.5",r:".5",fill:"currentColor",key:"1okk4w"}],["circle",{cx:"17.5",cy:"10.5",r:".5",fill:"currentColor",key:"f64h9f"}],["circle",{cx:"6.5",cy:"12.5",r:".5",fill:"currentColor",key:"qy21gx"}],["circle",{cx:"8.5",cy:"7.5",r:".5",fill:"currentColor",key:"fotxhn"}]],df=q("palette",ff);const hf=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M9 3v18",key:"fh3hqa"}],["path",{d:"m16 15-3-3 3-3",key:"14y99z"}]],pf=q("panel-left-close",hf);const gf=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M9 3v18",key:"fh3hqa"}],["path",{d:"m14 9 3 3-3 3",key:"8010ee"}]],mf=q("panel-left-open",gf);const yf=[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]],_f=q("plus",yf);const vf=[["path",{d:"M15.39 4.39a1 1 0 0 0 1.68-.474 2.5 2.5 0 1 1 3.014 3.015 1 1 0 0 0-.474 1.68l1.683 1.682a2.414 2.414 0 0 1 0 3.414L19.61 15.39a1 1 0 0 1-1.68-.474 2.5 2.5 0 1 0-3.014 3.015 1 1 0 0 1 .474 1.68l-1.683 1.682a2.414 2.414 0 0 1-3.414 0L8.61 19.61a1 1 0 0 0-1.68.474 2.5 2.5 0 1 1-3.014-3.015 1 1 0 0 0 .474-1.68l-1.683-1.682a2.414 2.414 0 0 1 0-3.414L4.39 8.61a1 1 0 0 1 1.68.474 2.5 2.5 0 1 0 3.014-3.015 1 1 0 0 1-.474-1.68l1.683-1.682a2.414 2.414 0 0 1 3.414 0z",key:"w46dr5"}]],bf=q("puzzle",vf);const xf=[["path",{d:"m13.5 8.5-5 5",key:"1cs55j"}],["path",{d:"m8.5 8.5 5 5",key:"a8mexj"}],["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["path",{d:"m21 21-4.3-4.3",key:"1qie3q"}]],wf=q("search-x",xf);const Sf=[["path",{d:"m21 21-4.34-4.34",key:"14j7rj"}],["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}]],Ef=q("search",Sf);const Rf=[["path",{d:"M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915",key:"1i5ecw"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]],Cf=q("settings",Rf);const kf=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],Af=q("shield-check",kf);const Pf=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}]],Tf=q("square",Pf);const Mf=[["circle",{cx:"12",cy:"12",r:"4",key:"4exip2"}],["path",{d:"M12 2v2",key:"tus03m"}],["path",{d:"M12 20v2",key:"1lh1kg"}],["path",{d:"m4.93 4.93 1.41 1.41",key:"149t6j"}],["path",{d:"m17.66 17.66 1.41 1.41",key:"ptbguv"}],["path",{d:"M2 12h2",key:"1t8f8n"}],["path",{d:"M20 12h2",key:"1q8mjw"}],["path",{d:"m6.34 17.66-1.41 1.41",key:"1m8zz5"}],["path",{d:"m19.07 4.93-1.41 1.41",key:"1shlcs"}]],Jr=q("sun",Mf);const $f=[["path",{d:"M13.172 2a2 2 0 0 1 1.414.586l6.71 6.71a2.4 2.4 0 0 1 0 3.408l-4.592 4.592a2.4 2.4 0 0 1-3.408 0l-6.71-6.71A2 2 0 0 1 6 9.172V3a1 1 0 0 1 1-1z",key:"16rjxf"}],["path",{d:"M2 7v6.172a2 2 0 0 0 .586 1.414l6.71 6.71a2.4 2.4 0 0 0 3.191.193",key:"178nd4"}],["circle",{cx:"10.5",cy:"6.5",r:".5",fill:"currentColor",key:"12ikhr"}]],Of=q("tags",$f);const jf=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],Nf=q("trash-2",jf);const If=[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}],["line",{x1:"22",x2:"16",y1:"11",y2:"11",key:"1shjgl"}]],Lf=q("user-minus",If);const Df=[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}],["line",{x1:"19",x2:"19",y1:"8",y2:"14",key:"1bvyxn"}],["line",{x1:"22",x2:"16",y1:"11",y2:"11",key:"1shjgl"}]],Ff=q("user-plus",Df);const Hf=[["path",{d:"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2",key:"975kel"}],["circle",{cx:"12",cy:"7",r:"4",key:"17ys0d"}]],Uf=q("user",Hf);const qf=[["path",{d:"M18 21a8 8 0 0 0-16 0",key:"3ypg7q"}],["circle",{cx:"10",cy:"8",r:"5",key:"o932ke"}],["path",{d:"M22 20c0-3.37-2-6.5-4-8a5 5 0 0 0-.45-8.3",key:"10s06x"}]],Vf=q("users-round",qf);const zf=[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["path",{d:"M16 3.128a4 4 0 0 1 0 7.744",key:"16gr8j"}],["path",{d:"M22 21v-2a4 4 0 0 0-3-3.87",key:"kshegd"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}]],Bf=q("users",zf);const Kf=[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]],Wf=q("x",Kf),Gf=Ie({});function Qf(e,t={}){const n=[];for(const[s,o]of Object.entries(t)){const i=s.replace(/^:/,"");if(i==="")continue;const l=String(o);n.push([`:${i.charAt(0).toUpperCase()}${i.slice(1)}`,l.charAt(0).toUpperCase()+l.slice(1)]),n.push([`:${i.toUpperCase()}`,l.toUpperCase()]),n.push([`:${i}`,l])}n.sort((s,o)=>o[0].length-s[0].length);let r=e;for(const[s,o]of n)r=r.split(s).join(o);return r}function we(e,t={}){const r=Gf.value[e]?.other;return r===void 0?e:Qf(r,t)}const no={archive:Ru,"arrow-down":ku,"arrow-left":Pu,"arrow-right":Mu,"arrow-up":Nu,"arrow-up-right":Ou,"cassette-tape":Lu,check:Fu,"chevron-down":Uu,"chevron-left":Vu,"chevron-right":ji,circle:Wu,"circle-alert":Ni,disc:Yu,"disc-3":Qu,image:Zu,library:tf,list:rf,"log-out":of,"mic-vocal":cf,minus:uf,palette:df,"panel-left-close":pf,"panel-left-open":mf,plus:_f,puzzle:bf,search:Ef,"search-x":wf,settings:Cf,"shield-check":Af,sun:Jr,tags:Of,"trash-2":Nf,user:Uf,"user-minus":Lf,"user-plus":Ff,users:Bf,"users-round":Vf,x:Wf},Jf={"icon-archive":"archive","icon-circle":"circle","icon-list-ul":"list","icon-tags":"tags","icon-user":"user","icon-search":"search","icon-cog":"settings","icon-users":"users"},ro=Tf;function Ii(e){if(!e)return ro;const t=e.trim();return no[t]??no[Jf[t]??""]??ro}const Yf=/\{[^{}]+\}/g,Xf=()=>typeof process=="object"&&Number.parseInt(process?.versions?.node?.substring(0,2))>=18&&process.versions.undici;function Zf(){return Math.random().toString(36).slice(2,11)}function ed(e){let{baseUrl:t="",Request:n=globalThis.Request,fetch:r=globalThis.fetch,querySerializer:s,bodySerializer:o,pathSerializer:i,headers:l,requestInitExt:c=void 0,...f}={...e};c=Xf()?c:void 0,t=io(t);const u=[];async function h(p,g){const{baseUrl:R,fetch:w=r,Request:I=n,headers:O,params:k={},parseAs:M="json",querySerializer:$,bodySerializer:G=o??nd,pathSerializer:me,body:se,middleware:He=[],...Ue}=g||{};let Oe=t;R&&(Oe=io(R)??t);let qe=typeof s=="function"?s:so(s);$&&(qe=typeof $=="function"?$:so({...typeof s=="object"?s:{},...$}));const Ye=me||i||td,je=se===void 0?void 0:G(se,oo(l,O,k.header)),St=oo(je===void 0||je instanceof FormData?{}:{"Content-Type":"application/json"},l,O,k.header),Ae=[...u,...He],he={redirect:"follow",...f,...Ue,body:je,headers:St};let K,z,ye=new I(rd(p,{baseUrl:Oe,params:k,querySerializer:qe,pathSerializer:Ye}),he),ee;for(const ue in Ue)ue in ye||(ye[ue]=Ue[ue]);if(Ae.length){K=Zf(),z=Object.freeze({baseUrl:Oe,fetch:w,parseAs:M,querySerializer:qe,bodySerializer:G,pathSerializer:Ye});for(const ue of Ae)if(ue&&typeof ue=="object"&&typeof ue.onRequest=="function"){const oe=await ue.onRequest({request:ye,schemaPath:p,params:k,options:z,id:K});if(oe)if(oe instanceof I)ye=oe;else if(oe instanceof Response){ee=oe;break}else throw new Error("onRequest: must return new Request() or Response() when modifying the request")}}if(!ee){try{ee=await w(ye,c)}catch(ue){let oe=ue;if(Ae.length)for(let Pe=Ae.length-1;Pe>=0;Pe--){const ve=Ae[Pe];if(ve&&typeof ve=="object"&&typeof ve.onError=="function"){const y=await ve.onError({request:ye,error:oe,schemaPath:p,params:k,options:z,id:K});if(y){if(y instanceof Response){oe=void 0,ee=y;break}if(y instanceof Error){oe=y;continue}throw new Error("onError: must return new Response() or instance of Error")}}}if(oe)throw oe}if(Ae.length)for(let ue=Ae.length-1;ue>=0;ue--){const oe=Ae[ue];if(oe&&typeof oe=="object"&&typeof oe.onResponse=="function"){const Pe=await oe.onResponse({request:ye,response:ee,schemaPath:p,params:k,options:z,id:K});if(Pe){if(!(Pe instanceof Response))throw new Error("onResponse: must return new Response() when modifying the response");ee=Pe}}}}const Le=ee.headers.get("Content-Length");if(ee.status===204||ye.method==="HEAD"||Le==="0"&&!ee.headers.get("Transfer-Encoding")?.includes("chunked"))return ee.ok?{data:void 0,response:ee}:{error:void 0,response:ee};if(ee.ok)return{data:await(async()=>{if(M==="stream")return ee.body;if(M==="json"&&!Le){const oe=await ee.text();return oe?JSON.parse(oe):void 0}return await ee[M]()})(),response:ee};let _e=await ee.text();try{_e=JSON.parse(_e)}catch{}return{error:_e,response:ee}}return{request(p,g,R){return h(g,{...R,method:p.toUpperCase()})},GET(p,g){return h(p,{...g,method:"GET"})},PUT(p,g){return h(p,{...g,method:"PUT"})},POST(p,g){return h(p,{...g,method:"POST"})},DELETE(p,g){return h(p,{...g,method:"DELETE"})},OPTIONS(p,g){return h(p,{...g,method:"OPTIONS"})},HEAD(p,g){return h(p,{...g,method:"HEAD"})},PATCH(p,g){return h(p,{...g,method:"PATCH"})},TRACE(p,g){return h(p,{...g,method:"TRACE"})},use(...p){for(const g of p)if(g){if(typeof g!="object"||!("onRequest"in g||"onResponse"in g||"onError"in g))throw new Error("Middleware must be an object with one of `onRequest()`, `onResponse() or `onError()`");u.push(g)}},eject(...p){for(const g of p){const R=u.indexOf(g);R!==-1&&u.splice(R,1)}}}}function Yn(e,t,n){if(t==null)return"";if(typeof t=="object")throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");return`${e}=${n?.allowReserved===!0?t:encodeURIComponent(t)}`}function Li(e,t,n){if(!t||typeof t!="object")return"";const r=[],s={simple:",",label:".",matrix:";"}[n.style]||"&";if(n.style!=="deepObject"&&n.explode===!1){for(const l in t)r.push(l,n.allowReserved===!0?t[l]:encodeURIComponent(t[l]));const i=r.join(",");switch(n.style){case"form":return`${e}=${i}`;case"label":return`.${i}`;case"matrix":return`;${e}=${i}`;default:return i}}for(const i in t){const l=n.style==="deepObject"?`${e}[${i}]`:i;r.push(Yn(l,t[i],n))}const o=r.join(s);return n.style==="label"||n.style==="matrix"?`${s}${o}`:o}function Di(e,t,n){if(!Array.isArray(t))return"";if(n.explode===!1){const o={form:",",spaceDelimited:"%20",pipeDelimited:"|"}[n.style]||",",i=(n.allowReserved===!0?t:t.map(l=>encodeURIComponent(l))).join(o);switch(n.style){case"simple":return i;case"label":return`.${i}`;case"matrix":return`;${e}=${i}`;default:return`${e}=${i}`}}const r={simple:",",label:".",matrix:";"}[n.style]||"&",s=[];for(const o of t)n.style==="simple"||n.style==="label"?s.push(n.allowReserved===!0?o:encodeURIComponent(o)):s.push(Yn(e,o,n));return n.style==="label"||n.style==="matrix"?`${r}${s.join(r)}`:s.join(r)}function so(e){return function(n){const r=[];if(n&&typeof n=="object")for(const s in n){const o=n[s];if(o!=null){if(Array.isArray(o)){if(o.length===0)continue;r.push(Di(s,o,{style:"form",explode:!0,...e?.array,allowReserved:e?.allowReserved||!1}));continue}if(typeof o=="object"){r.push(Li(s,o,{style:"deepObject",explode:!0,...e?.object,allowReserved:e?.allowReserved||!1}));continue}r.push(Yn(s,o,e))}}return r.join("&")}}function td(e,t){let n=e;for(const r of e.match(Yf)??[]){let s=r.substring(1,r.length-1),o=!1,i="simple";if(s.endsWith("*")&&(o=!0,s=s.substring(0,s.length-1)),s.startsWith(".")?(i="label",s=s.substring(1)):s.startsWith(";")&&(i="matrix",s=s.substring(1)),!t||t[s]===void 0||t[s]===null)continue;const l=t[s];if(Array.isArray(l)){n=n.replace(r,Di(s,l,{style:i,explode:o}));continue}if(typeof l=="object"){n=n.replace(r,Li(s,l,{style:i,explode:o}));continue}if(i==="matrix"){n=n.replace(r,`;${Yn(s,l)}`);continue}n=n.replace(r,i==="label"?`.${encodeURIComponent(l)}`:encodeURIComponent(l))}return n}function nd(e,t){return e instanceof FormData?e:t&&(t.get instanceof Function?t.get("Content-Type")??t.get("content-type"):t["Content-Type"]??t["content-type"])==="application/x-www-form-urlencoded"?new URLSearchParams(e).toString():JSON.stringify(e)}function rd(e,t){let n=`${t.baseUrl}${e}`;t.params?.path&&(n=t.pathSerializer(n,t.params.path));let r=t.querySerializer(t.params.query??{});return r.startsWith("?")&&(r=r.substring(1)),r&&(n+=`?${r}`),n}function oo(...e){const t=new Headers;for(const n of e){if(!n||typeof n!="object")continue;const r=n instanceof Headers?n.entries():Object.entries(n);for(const[s,o]of r)if(o===null)t.delete(s);else if(Array.isArray(o))for(const i of o)t.append(s,i);else o!==void 0&&t.set(s,o)}return t}function io(e){return e.endsWith("/")?e.substring(0,e.length-1):e}const lo="/backend",sd="__SUMMER_ADMIN_BASE__";function od(e=document){const t=e.querySelector('meta[name="summer-admin-base"]')?.content.trim()??"";if(t===""||t===sd||!t.startsWith("/"))return lo;const n=t.replace(/\/+$/,"");return n===""?lo:n}const co=od(),Fi={base:co,api:`${co}/api/v1`},id="XMLHttpRequest";let Hi=null;function ld(e){Hi=e}function cd(e){return new URL(e.url,"http://local").pathname.endsWith("/auth/login")}const ad={onRequest({request:e}){return e.headers.set("X-Requested-With",id),e},onResponse({request:e,response:t}){return t.status===401&&!cd(e)&&Hi?.(),t}},Kt=ed({baseUrl:Fi.api,credentials:"same-origin",fetch:e=>globalThis.fetch(e)});Kt.use(ad);const ud=/^[A-Za-z0-9_-]+$/;function Yr(e){const t=e.split(".");if(t.length!==3||!t.every(o=>ud.test(o)))return null;const[n,r,s]=t;return{vendor:n,plugin:r,controller:s}}function kr(e){const t=Yr(e);return t?`/${t.vendor}/${t.plugin}/${t.controller}`:null}function fd(e){return`${e.vendor}.${e.plugin}.${e.controller}`}function Ui(e){const t=e.replace(/^\/+|\/+$/g,"").split("/");if(t.length<3)return null;const n=t.slice(0,3).join(".");return Yr(n)?n:null}function ao(e){const t=Yr(e);return t?`${t.vendor}.${t.plugin}`:null}const Ar=Ie([]);async function qi(){const{data:e,response:t}=await Kt.GET("/navigation");return Ar.value=t.ok&&e?e.data:[],Ar.value}const Xr=ce(()=>Ar.value.filter(e=>e.sideMenu.length>0).map((e,t)=>({entry:e,index:t})).sort((e,t)=>e.entry.order-t.entry.order||e.index-t.index).map(({entry:e})=>e));function Vi(e){for(const t of e.sideMenu){const n=kr(t.controller);if(n)return n}return kr(e.controller)}function Zr(e,t){const n=`${e}.${t}`;return Xr.value.find(r=>ao(r.controller)===n||r.sideMenu.some(s=>ao(s.controller)===n))??null}function dd(){for(const e of Xr.value){const t=Vi(e);if(t)return t}return null}const hd=["aria-label"],pd={class:"mb-3.5 flex size-10 items-center justify-center rounded-inner bg-accent-soft",title:"SummerCMS"},gd=lt({__name:"PluginRail",setup(e){const t=Wt(),n=ce(()=>Zr(String(t.params.vendor??""),String(t.params.plugin??""))),r=ce(()=>Xr.value.map(s=>({entry:s,to:Vi(s)??"/",icon:Ii(s.icon),current:n.value?.code===s.code})));return(s,o)=>(V(),le("nav",{"aria-label":X(we)("backend::lang.nav.plugins"),class:"flex w-rail shrink-0 flex-col items-center gap-1.5 border-r border-side-border bg-side pt-3.5 pb-3"},[H("span",pd,[ge(X(Jr),{size:22,class:"text-accent","aria-hidden":"true"})]),(V(!0),le(fe,null,kt(r.value,i=>(V(),Qe(X(Qr),{key:i.entry.code,to:i.to,"aria-current":i.current?"page":void 0,class:wt([i.current?"bg-accent-soft font-bold text-accent":"text-side-text hover:bg-side-hover hover:text-white","flex w-[68px] flex-col items-center gap-1 rounded-inner px-1.5 py-2 text-center text-[11px] leading-[1.2] tracking-[-0.01em] no-underline"])},{default:zn(()=>[(V(),Qe(Yo(i.icon),{size:20,"aria-hidden":"true"})),H("span",null,de(i.entry.label),1)]),_:2},1032,["to","aria-current","class"]))),128))],8,hd))}}),md=["aria-label"],yd={class:"mb-3.5 flex h-10 items-center px-2"},_d={class:"text-[16px] font-bold"},vd=lt({__name:"SectionPanel",props:{entry:{}},setup(e){const t=e,n=Wt(),r=ce(()=>Ui(n.path)),s=ce(()=>t.entry.sideMenu.flatMap(o=>{const i=kr(o.controller);return i?[{item:o,to:i,icon:Ii(o.icon),current:r.value===o.controller}]:[]}));return(o,i)=>(V(),le("aside",{"aria-label":X(we)("backend::lang.nav.sections"),class:"flex w-panel shrink-0 flex-col gap-0.5 border-r border-border bg-surface px-3 py-3.5"},[H("div",yd,[H("span",_d,de(e.entry.label),1)]),(V(!0),le(fe,null,kt(s.value,l=>(V(),Qe(X(Qr),{key:l.item.code,to:l.to,"aria-current":l.current?"page":void 0,class:wt([l.current?"bg-sel font-semibold text-text":"font-medium text-muted hover:bg-hover hover:text-text","flex h-nav items-center gap-3 rounded-control px-3 no-underline"])},{default:zn(()=>[(V(),Qe(Yo(l.icon),{size:18,"aria-hidden":"true"})),H("span",null,de(l.item.label),1)]),_:2},1032,["to","aria-current","class"]))),128))],8,md))}}),Nn=Ie(null),zi=Ie(null),en=Cn(Nn);async function bd(e,t){const{data:n,response:r}=await Kt.POST("/auth/login",{body:{login:e,password:t}});return!r.ok||!n?!1:(zi.value=typeof n.data.expires_in=="number"?n.data.expires_in:null,!0)}async function Bi(){const{data:e,response:t}=await Kt.GET("/auth/me");return Nn.value=t.ok&&e?e.data:null,Nn.value}function xd(){Nn.value=null,zi.value=null}const wd={class:"flex min-h-screen bg-bg text-text"},Sd={class:"flex min-w-0 flex-1 flex-col"},Ed={class:"flex h-header shrink-0 items-center justify-between border-b border-border bg-surface pr-6 pl-8"},Rd=["aria-label"],Cd={class:"font-semibold","aria-current":"page"},kd={key:0,class:"flex h-input items-center gap-2.5 rounded-inner px-2"},Ad={class:"flex size-[34px] items-center justify-center rounded-full bg-accent text-[13px] font-bold text-on-accent","aria-hidden":"true"},Pd={class:"flex flex-col leading-tight"},Td={class:"font-semibold"},Md={key:0,class:"text-[12px] text-muted"},$d={class:"min-w-0 flex-1 px-8 py-7"},Od=lt({__name:"AppShell",setup(e){const t=Wt(),n=ce(()=>Zr(String(t.params.vendor??""),String(t.params.plugin??""))),r=ce(()=>{const i=Ui(t.path);return n.value?.sideMenu.find(l=>l.controller===i)??null}),s=ce(()=>{const i=en.value;if(!i)return"";const l=`${i.first_name} ${i.last_name}`.trim();return l===""?i.login:l}),o=ce(()=>s.value.split(/\s+/).filter(Boolean).slice(0,2).map(i=>i.charAt(0).toUpperCase()).join(""));return(i,l)=>(V(),le("div",wd,[ge(gd),n.value?(V(),Qe(vd,{key:0,entry:n.value},null,8,["entry"])):jt("",!0),H("div",Sd,[H("header",Ed,[H("nav",{"aria-label":X(we)("backend::lang.nav.breadcrumbs"),class:"flex items-center gap-2"},[n.value?(V(),le(fe,{key:0},[H("span",{class:wt(r.value?"text-muted":"font-semibold")},de(n.value.label),3),r.value?(V(),le(fe,{key:0},[ge(X(ji),{size:14,class:"text-muted","aria-hidden":"true"}),H("span",Cd,de(r.value.label),1)],64)):jt("",!0)],64)):jt("",!0)],8,Rd),X(en)?(V(),le("div",kd,[H("span",Ad,de(o.value),1),H("span",Pd,[H("span",Td,de(s.value),1),X(en).role?(V(),le("span",Md,de(X(en).role.name),1)):jt("",!0)])])):jt("",!0)]),H("main",$d,[Zl(i.$slots,"default")])])]))}}),jd=lt({__name:"App",setup(e){const t=Wt(),n=ce(()=>t.meta.shell===!0);return(r,s)=>n.value?(V(),Qe(Od,{key:0},{default:zn(()=>[ge(X(Cr))]),_:1})):(V(),Qe(X(Cr),{key:1}))}}),Nd={class:"flex min-h-screen items-center justify-center px-4",style:{background:"radial-gradient(ellipse at 50% 35%, #2b3a5c 0%, #1d2740 60%)"}},Id={class:"flex w-full max-w-[400px] flex-col gap-6"},Ld={class:"flex items-center justify-center gap-3"},Dd={class:"flex size-[34px] items-center justify-center rounded-full bg-accent-soft"},Fd={class:"text-[22px] font-bold tracking-[-0.02em]"},Hd={class:"flex flex-col gap-1.5"},Ud={class:"font-semibold"},qd={class:"flex flex-col gap-1.5"},Vd={class:"font-semibold"},zd=["aria-invalid"],Bd={key:0,role:"alert",class:"flex items-center gap-2.5 rounded-control bg-[#fdf0f0] px-3.5 py-3 text-[#c62828]"},Kd=["disabled"],Wd=lt({__name:"LoginView",setup(e){const t=Wt(),n=_a(),r=Ie(""),s=Ie(""),o=Ie(!1),i=Ie(!1);async function l(){if(!i.value){i.value=!0,o.value=!1;try{if(!await bd(r.value.trim(),s.value)||!await Bi()){o.value=!0;return}s.value="",await qi(),await n.replace(Ki(t.query.redirect)??"/")}finally{i.value=!1}}}return(c,f)=>(V(),le("main",Nd,[H("div",Id,[H("div",Ld,[H("span",Dd,[ge(X(Jr),{size:20,class:"text-accent","aria-hidden":"true"})]),f[2]||(f[2]=H("span",{class:"text-[18px] font-bold text-white"},[_i("Summer"),H("span",{class:"text-accent"},"CMS")],-1))]),H("form",{class:"flex flex-col gap-[18px] rounded-card bg-white p-8 text-[#141b2d] shadow-login",novalidate:"",onSubmit:ua(l,["prevent"])},[H("h1",Fd,de(X(we)("backend::lang.auth.title")),1),H("label",Hd,[H("span",Ud,de(X(we)("backend::lang.auth.login")),1),cs(H("input",{"onUpdate:modelValue":f[0]||(f[0]=u=>r.value=u),name:"login",type:"text",autocomplete:"username",required:"",class:"h-input rounded-control border border-[#d2d8e2] bg-white px-3.5"},null,512),[[Is,r.value]])]),H("label",qd,[H("span",Vd,de(X(we)("backend::lang.auth.password")),1),cs(H("input",{"onUpdate:modelValue":f[1]||(f[1]=u=>s.value=u),name:"password",type:"password",autocomplete:"current-password",required:"","aria-invalid":o.value?"true":void 0,class:wt([o.value?"border-[#c62828]":"border-[#d2d8e2]","h-input rounded-control border bg-white px-3.5"])},null,10,zd),[[Is,s.value]])]),o.value?(V(),le("div",Bd,[ge(X(Ni),{size:18,"aria-hidden":"true"}),H("span",null,de(X(we)("backend::lang.auth.invalid")),1)])):jt("",!0),H("button",{type:"submit",disabled:i.value,class:"h-input rounded-control bg-[#22304d] font-semibold text-white disabled:opacity-60"},de(X(we)("backend::lang.auth.submit")),9,Kd)],32)])]))}}),Gd={class:"overflow-x-auto"},Qd={class:"w-full min-w-[640px] border-collapse text-left"},Jd={class:"h-row-head border-y border-border bg-subtle"},Yd=["aria-busy"],Xd={key:1},Zd=["colspan"],eh=lt({__name:"DataTable",props:{columns:{},rows:{},loading:{type:Boolean},emptyText:{}},setup(e){function t(s,o){return n(s[o.key])}function n(s){if(s==null||s==="")return"—";if(Array.isArray(s)){const o=s.map(n).filter(i=>i!=="—");return o.length===0?"—":o.join(", ")}return typeof s=="boolean"?we(s?"backend::lang.list.column_switch_true":"backend::lang.list.column_switch_false"):typeof s=="object"?"—":String(s)}function r(s,o){const i=s.id;return typeof i=="number"||typeof i=="string"?String(i):`row-${o}`}return(s,o)=>(V(),le("div",Gd,[H("table",Qd,[H("thead",null,[H("tr",Jd,[(V(!0),le(fe,null,kt(e.columns,i=>(V(),le("th",{key:i.key,scope:"col",class:"px-3.5 text-[12px] font-semibold text-muted first:pl-5 last:pr-5"},de(i.label),1))),128))])]),H("tbody",{"aria-busy":e.loading?"true":void 0},[e.loading?(V(),le(fe,{key:0},kt(8,i=>H("tr",{key:`skeleton-${i}`,class:"h-row border-b border-border"},[(V(!0),le(fe,null,kt(e.columns,l=>(V(),le("td",{key:l.key,class:"px-3.5 first:pl-5 last:pr-5"},[...o[0]||(o[0]=[H("span",{class:"block h-3 w-3/5 rounded-[6px] bg-skel"},null,-1)])]))),128))])),64)):e.rows.length===0?(V(),le("tr",Xd,[H("td",{colspan:Math.max(e.columns.length,1),class:"px-5 py-20 text-center text-muted"},de(e.emptyText??X(we)("backend::lang.list.no_records")),9,Zd)])):(V(!0),le(fe,{key:2},kt(e.rows,(i,l)=>(V(),le("tr",{key:r(i,l),class:"h-row border-b border-border"},[(V(!0),le(fe,null,kt(e.columns,(c,f)=>(V(),le("td",{key:c.key,class:wt([f===0?"font-semibold":"text-muted","px-3.5 first:pl-5 last:pr-5"])},de(t(i,c)),3))),128))]))),128))],8,Yd)])]))}}),th={class:"flex flex-col gap-5"},nh={class:"text-[26px] font-bold tracking-[-0.02em]"},rh={class:"overflow-hidden rounded-card border border-border bg-surface shadow-card"},sh={key:0,role:"alert",class:"px-5 py-4 text-danger"},oh={class:"border-t border-border px-5 py-3.5 text-[13px] text-muted"},ih=lt({__name:"ListView",setup(e){const t=Wt(),n=ce(()=>({vendor:String(t.params.vendor??""),plugin:String(t.params.plugin??""),controller:String(t.params.controller??"")})),r=Ie(null),s=Ie([]),o=Ie(null),i=Ie(!0),l=Ie(!1),c=ce(()=>{if(r.value?.title)return r.value.title;const p=fd(n.value);return Zr(n.value.vendor,n.value.plugin)?.sideMenu.find(R=>R.controller===p)?.label??p}),f=ce(()=>{const p=o.value;if(!p||p.total===0)return we("backend::lang.list.no_results");const g=(p.page-1)*p.per_page+1,R=Math.min(p.page*p.per_page,p.total);return we("backend::lang.list.pagination_range",{from:g,to:R,total:p.total})});let u=0;async function h(){const p=++u;i.value=!0,l.value=!1;const g={path:n.value},[R,w]=await Promise.all([Kt.GET("/{vendor}/{plugin}/{controller}/schema/list",{params:g}),Kt.GET("/{vendor}/{plugin}/{controller}",{params:g})]);p===u&&(r.value=R.data?.data??null,s.value=w.data?.data??[],o.value=w.data?.meta??null,l.value=!R.data||!w.data,i.value=!1)}return sn(n,h,{immediate:!0,deep:!0}),(p,g)=>(V(),le("section",th,[H("header",null,[H("h1",nh,de(c.value),1)]),H("div",rh,[l.value&&!i.value?(V(),le("p",sh,de(X(we)("backend::lang.list.load_failed")),1)):(V(),Qe(eh,{key:1,columns:r.value?.columns??[],rows:s.value,loading:i.value,"empty-text":r.value?.noRecordsMessage},null,8,["columns","rows","loading","empty-text"])),H("footer",oh,de(i.value?X(we)("backend::lang.list.loading"):f.value),1)])]))}}),lh={class:"flex flex-col items-center gap-2 py-20 text-center"},ch={class:"text-[17px] font-bold"},uo=lt({__name:"NotFoundView",props:{home:{type:Boolean}},setup(e){return(t,n)=>(V(),le("section",lh,[H("h1",ch,de(e.home?X(we)("backend::lang.nav.empty"):X(we)("backend::lang.page.not_found")),1)]))}});function Ki(e){return typeof e!="string"||e.length===0||!e.startsWith("/")||e.startsWith("//")||e.startsWith("/\\")?null:e}function ah(e=Xa(Fi.base)){const t=yu({history:e,routes:[{path:"/login",name:"login",component:Wd,meta:{public:!0}},{path:"/",name:"home",component:uo,props:{home:!0},meta:{shell:!0},beforeEnter:()=>dd()??!0},{path:"/:vendor/:plugin/:controller",name:"list",component:ih,meta:{shell:!0}},{path:"/:pathMatch(.*)*",name:"not-found",component:uo,meta:{shell:!0}}]});return t.beforeEach(n=>{const r=en.value!==null;return n.meta.public?r&&n.name==="login"?Ki(n.query.redirect)??"/":!0:r?!0:{name:"login",query:{redirect:n.fullPath}}}),t}async function uh(){await Bi().catch(()=>null)&&await qi().catch(()=>[]);const t=ah();ld(()=>{xd();const r=t.currentRoute.value;r.name!=="login"&&t.push({name:"login",query:{redirect:r.fullPath}})});const n=ha(jd);n.use(t),await t.isReady(),n.mount("#app")}uh(); diff --git a/boardwalk/dist/index.html b/boardwalk/dist/index.html new file mode 100644 index 0000000..9ea91a2 --- /dev/null +++ b/boardwalk/dist/index.html @@ -0,0 +1,15 @@ + + + + + + + + SummerCMS + + + + +
+ + diff --git a/bouncer/jwt.go b/bouncer/jwt.go index 30e014b..3c38508 100644 --- a/bouncer/jwt.go +++ b/bouncer/jwt.go @@ -87,13 +87,16 @@ func NewJWTGuard(secret string, users UserProvider, bl BlacklistStore, cookieNam return &jwtGuard{secret: secret, users: users, bl: bl, cookieNames: cookieNames} } -// NewBackendJWTGuard is bearer-only and requires AudienceBackend. -// write may replace the PHP-shaped 401 body; nil keeps write401. -func NewBackendJWTGuard(secret string, users UserProvider, bl BlacklistStore, write func(http.ResponseWriter, error)) Guard { +// NewBackendJWTGuard requires AudienceBackend. write may replace the +// PHP-shaped 401 body; nil keeps write401. With no cookieNames it is +// Bearer-only; otherwise each cookie is tried, in order, after the +// Authorization header, so a Bearer token still wins when both are sent. +func NewBackendJWTGuard(secret string, users UserProvider, bl BlacklistStore, write func(http.ResponseWriter, error), cookieNames ...string) Guard { return &jwtGuard{ secret: secret, users: users, bl: bl, + cookieNames: cookieNames, audience: AudienceBackend, requireAudience: true, writeFn: write, diff --git a/cabana/admin_openapi.go b/cabana/admin_openapi.go index 5516a0d..108dfa8 100644 --- a/cabana/admin_openapi.go +++ b/cabana/admin_openapi.go @@ -1,9 +1,19 @@ package cabana -// Admin API annotations. swag reads these with the handler package so -// docs/openapi.json lists every D-09 route. The functions are not mounted; -// service.mount in http.go is the runtime route table, and -// TestPhase09PermissionMatrix fails if the two lists diverge. +// @title SummerCMS Admin API +// @version 1 +// @description Framework admin API consumed by the embedded admin SPA. Every path is relative to {backend.uri}/api/v1 (for example /backend/api/v1). The SPA authenticates with the HttpOnly summer_admin cookie set by a login that sends X-Requested-With: XMLHttpRequest, and sends that header on every request; CLI clients and tests send the BackendBearer Authorization header instead. +// @BasePath / +// @securityDefinitions.apikey BackendBearer +// @in header +// @name Authorization +// @description Backend admin bearer token. Send "Bearer {access_token}". + +// Admin API annotations. scripts/check-admin-openapi.sh reads them with swag +// to produce admin/openapi/admin.json, the document the SPA's TypeScript types +// are generated from (D-15). The functions are not mounted; service.mount in +// http.go is the runtime route table, and TestPhase09PermissionMatrix plus +// TestPhase09ContractInventory fail if the two lists diverge. // ErrorBody is one D-10 error object. type ErrorBody struct { @@ -32,41 +42,84 @@ type SuccessEnvelope struct { Meta SuccessMeta `json:"meta"` } -// AdminLoginData is the admin login payload. +// AdminLoginData is the admin login and refresh payload. Bearer transport +// carries access_token; cookie transport (X-Requested-With: XMLHttpRequest) +// carries token_type "cookie" and expires_in, never the token. type AdminLoginData struct { - AccessToken string `json:"access_token"` + AccessToken string `json:"access_token,omitempty"` TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in,omitempty"` } -// AdminLoginEnvelope is the admin login success body. -type AdminLoginEnvelope struct { - Data AdminLoginData `json:"data"` - Meta SuccessMeta `json:"meta"` +// Envelope is the typed D-10 success envelope. +type Envelope[T any] struct { + Data T `json:"data"` + Meta SuccessMeta `json:"meta"` } -// AdminLogin documents POST /_admin/api/v1/auth/login. +// ListEnvelope is the typed D-10 paginated envelope (Phase 9 D-11 meta). +type ListEnvelope[T any] struct { + Data T `json:"data"` + Meta ListMeta `json:"meta"` +} + +// AdminRecord is one admin record: a string-keyed map read through its +// list or form schema (D-16). +type AdminRecord map[string]any + +// AdminLoginRequest is the admin login body. Either login or email +// identifies the backend user. +type AdminLoginRequest struct { + Login string `json:"login,omitempty"` + Email string `json:"email,omitempty"` + Password string `json:"password"` +} + +// AdminRoleSummary is the role attached to an admin profile. +type AdminRoleSummary struct { + ID uint `json:"id"` + Code string `json:"code"` + Name string `json:"name"` +} + +// AdminProfile is the GET /auth/me payload. +type AdminProfile struct { + ID uint `json:"id"` + Login string `json:"login"` + Email string `json:"email"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + IsSuperuser bool `json:"is_superuser"` + Role *AdminRoleSummary `json:"role,omitempty"` +} + +// AdminLogin documents POST /auth/login. // // @Summary Admin login // @Tags admin // @Accept json // @Produce json -// @Success 200 {object} AdminLoginEnvelope +// @Param body body AdminLoginRequest true "Credentials" +// @Param X-Requested-With header string false "XMLHttpRequest selects cookie transport" +// @Success 200 {object} Envelope[AdminLoginData] // @Failure 401 {object} ErrorEnvelope -// @Router /_admin/api/v1/auth/login [post] +// @Router /auth/login [post] func AdminLogin() {} -// AdminRefresh documents POST /_admin/api/v1/auth/refresh. +// AdminRefresh documents POST /auth/refresh. // // @Summary Refresh an admin token // @Tags admin // @Accept json // @Produce json -// @Success 200 {object} AdminLoginEnvelope +// @Param X-Requested-With header string false "XMLHttpRequest; required unless a Bearer token is sent" +// @Success 200 {object} Envelope[AdminLoginData] +// @Failure 403 {object} ErrorEnvelope // @Failure 401 {object} ErrorEnvelope -// @Router /_admin/api/v1/auth/refresh [post] +// @Router /auth/refresh [post] func AdminRefresh() {} -// AdminLogout documents POST /_admin/api/v1/auth/logout. +// AdminLogout documents POST /auth/logout. // // @Summary Admin logout // @Tags admin @@ -74,33 +127,33 @@ func AdminRefresh() {} // @Security BackendBearer // @Success 200 {object} SuccessEnvelope // @Failure 401 {object} ErrorEnvelope -// @Router /_admin/api/v1/auth/logout [post] +// @Router /auth/logout [post] func AdminLogout() {} -// AdminMe documents GET /_admin/api/v1/auth/me. +// AdminMe documents GET /auth/me. // // @Summary Current admin // @Tags admin // @Produce json // @Security BackendBearer -// @Success 200 {object} SuccessEnvelope +// @Success 200 {object} Envelope[AdminProfile] // @Failure 401 {object} ErrorEnvelope -// @Router /_admin/api/v1/auth/me [get] +// @Router /auth/me [get] func AdminMe() {} -// AdminNavigation documents GET /_admin/api/v1/navigation. +// AdminNavigation documents GET /navigation. // // @Summary Admin navigation // @Tags admin // @Produce json // @Security BackendBearer -// @Success 200 {object} SuccessEnvelope +// @Success 200 {object} Envelope[[]NavigationEntry] // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope -// @Router /_admin/api/v1/navigation [get] +// @Router /navigation [get] func AdminNavigation() {} -// AdminSettingsList documents GET /_admin/api/v1/settings. +// AdminSettingsList documents GET /settings. // // @Summary List admin settings // @Tags admin @@ -109,10 +162,10 @@ func AdminNavigation() {} // @Success 200 {object} SuccessEnvelope // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope -// @Router /_admin/api/v1/settings [get] +// @Router /settings [get] func AdminSettingsList() {} -// AdminSettingsSchema documents GET /_admin/api/v1/settings/{code}/schema. +// AdminSettingsSchema documents GET /settings/{code}/schema. // // @Summary Admin settings schema // @Tags admin @@ -123,10 +176,10 @@ func AdminSettingsList() {} // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 404 {object} ErrorEnvelope -// @Router /_admin/api/v1/settings/{code}/schema [get] +// @Router /settings/{code}/schema [get] func AdminSettingsSchema() {} -// AdminSettingsGet documents GET /_admin/api/v1/settings/{code}. +// AdminSettingsGet documents GET /settings/{code}. // // @Summary Read admin settings // @Tags admin @@ -137,10 +190,10 @@ func AdminSettingsSchema() {} // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 404 {object} ErrorEnvelope -// @Router /_admin/api/v1/settings/{code} [get] +// @Router /settings/{code} [get] func AdminSettingsGet() {} -// AdminSettingsPut documents PUT /_admin/api/v1/settings/{code}. +// AdminSettingsPut documents PUT /settings/{code}. // // @Summary Update admin settings // @Tags admin @@ -152,7 +205,7 @@ func AdminSettingsGet() {} // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 422 {object} ErrorEnvelope -// @Router /_admin/api/v1/settings/{code} [put] +// @Router /settings/{code} [put] func AdminSettingsPut() {} // AdminListSchema documents the list schema route. @@ -164,11 +217,11 @@ func AdminSettingsPut() {} // @Param vendor path string true "Vendor" // @Param plugin path string true "Plugin" // @Param controller path string true "Controller" -// @Success 200 {object} SuccessEnvelope +// @Success 200 {object} Envelope[ListSchema] // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 404 {object} ErrorEnvelope -// @Router /_admin/api/v1/{vendor}/{plugin}/{controller}/schema/list [get] +// @Router /{vendor}/{plugin}/{controller}/schema/list [get] func AdminListSchema() {} // AdminFormSchema documents the form schema route. @@ -184,7 +237,7 @@ func AdminListSchema() {} // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 404 {object} ErrorEnvelope -// @Router /_admin/api/v1/{vendor}/{plugin}/{controller}/schema/form [get] +// @Router /{vendor}/{plugin}/{controller}/schema/form [get] func AdminFormSchema() {} // AdminRelationSchema documents the relation schema route. @@ -201,7 +254,7 @@ func AdminFormSchema() {} // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 404 {object} ErrorEnvelope -// @Router /_admin/api/v1/{vendor}/{plugin}/{controller}/schema/relation/{name} [get] +// @Router /{vendor}/{plugin}/{controller}/schema/relation/{name} [get] func AdminRelationSchema() {} // AdminList documents the record list route. @@ -213,11 +266,16 @@ func AdminRelationSchema() {} // @Param vendor path string true "Vendor" // @Param plugin path string true "Plugin" // @Param controller path string true "Controller" -// @Success 200 {object} SuccessEnvelope +// @Param search query string false "Search term" +// @Param sort query string false "Sort column" +// @Param dir query string false "Sort direction (asc or desc)" +// @Param page query integer false "Page" +// @Param per_page query integer false "Records per page" +// @Success 200 {object} ListEnvelope[[]AdminRecord] // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 422 {object} ErrorEnvelope -// @Router /_admin/api/v1/{vendor}/{plugin}/{controller} [get] +// @Router /{vendor}/{plugin}/{controller} [get] func AdminList() {} // AdminCreate documents the record create route. @@ -234,7 +292,7 @@ func AdminList() {} // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 422 {object} ErrorEnvelope -// @Router /_admin/api/v1/{vendor}/{plugin}/{controller} [post] +// @Router /{vendor}/{plugin}/{controller} [post] func AdminCreate() {} // AdminBulkDelete documents the bulk delete route. @@ -251,7 +309,7 @@ func AdminCreate() {} // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 422 {object} ErrorEnvelope -// @Router /_admin/api/v1/{vendor}/{plugin}/{controller}/bulk-delete [post] +// @Router /{vendor}/{plugin}/{controller}/bulk-delete [post] func AdminBulkDelete() {} // AdminShow documents the record show route. @@ -268,7 +326,7 @@ func AdminBulkDelete() {} // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 404 {object} ErrorEnvelope -// @Router /_admin/api/v1/{vendor}/{plugin}/{controller}/{id} [get] +// @Router /{vendor}/{plugin}/{controller}/{id} [get] func AdminShow() {} // AdminUpdate documents the record update route. @@ -286,7 +344,7 @@ func AdminShow() {} // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 422 {object} ErrorEnvelope -// @Router /_admin/api/v1/{vendor}/{plugin}/{controller}/{id} [put] +// @Router /{vendor}/{plugin}/{controller}/{id} [put] func AdminUpdate() {} // AdminDelete documents the record delete route. @@ -303,7 +361,7 @@ func AdminUpdate() {} // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 404 {object} ErrorEnvelope -// @Router /_admin/api/v1/{vendor}/{plugin}/{controller}/{id} [delete] +// @Router /{vendor}/{plugin}/{controller}/{id} [delete] func AdminDelete() {} // AdminRelationLinked documents the linked-relation route. @@ -320,7 +378,7 @@ func AdminDelete() {} // @Success 200 {object} SuccessEnvelope // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope -// @Router /_admin/api/v1/{vendor}/{plugin}/{controller}/{id}/relations/{name} [get] +// @Router /{vendor}/{plugin}/{controller}/{id}/relations/{name} [get] func AdminRelationLinked() {} // AdminRelationCandidates documents the relation candidate route. @@ -337,7 +395,7 @@ func AdminRelationLinked() {} // @Success 200 {object} SuccessEnvelope // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope -// @Router /_admin/api/v1/{vendor}/{plugin}/{controller}/{id}/relations/{name}/candidates [get] +// @Router /{vendor}/{plugin}/{controller}/{id}/relations/{name}/candidates [get] func AdminRelationCandidates() {} // AdminRelationLink documents the relation link route. @@ -356,7 +414,7 @@ func AdminRelationCandidates() {} // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 422 {object} ErrorEnvelope -// @Router /_admin/api/v1/{vendor}/{plugin}/{controller}/{id}/relations/{name}/link [post] +// @Router /{vendor}/{plugin}/{controller}/{id}/relations/{name}/link [post] func AdminRelationLink() {} // AdminRelationUnlink documents the relation unlink route. @@ -375,5 +433,5 @@ func AdminRelationLink() {} // @Failure 401 {object} ErrorEnvelope // @Failure 403 {object} ErrorEnvelope // @Failure 422 {object} ErrorEnvelope -// @Router /_admin/api/v1/{vendor}/{plugin}/{controller}/{id}/relations/{name}/unlink [post] +// @Router /{vendor}/{plugin}/{controller}/{id}/relations/{name}/unlink [post] func AdminRelationUnlink() {} diff --git a/cabana/admin_paths_test.go b/cabana/admin_paths_test.go new file mode 100644 index 0000000..e5e7da6 --- /dev/null +++ b/cabana/admin_paths_test.go @@ -0,0 +1,7 @@ +package cabana + +// adminAPI composes a full admin API path under the default prefix, so tests +// follow the backend.uri scheme (D-03) instead of hardcoding it. +func adminAPI(rel string) string { + return DefaultAdminPrefix + adminAPIVersion + rel +} diff --git a/cabana/auth.go b/cabana/auth.go index ac39d7d..2a0e4df 100644 --- a/cabana/auth.go +++ b/cabana/auth.go @@ -172,12 +172,39 @@ func (s *service) login(w http.ResponseWriter, r *http.Request) { return } s.logAuth(r, "success", user.ID) + if isAjax(r) { + // Cookie transport (D-19): the SPA never sees the token. + s.writeSessionCookie(w, token) + WriteData(w, http.StatusOK, cookieLoginData(s.ttl), map[string]any{}) + return + } WriteData(w, http.StatusOK, map[string]string{ "access_token": token, "token_type": "bearer", }, map[string]any{}) } +// cookieLoginData is the login/refresh body under cookie transport: no token, +// only its type and the access lifetime in seconds. +func cookieLoginData(ttl time.Duration) AdminLoginData { + return AdminLoginData{TokenType: "cookie", ExpiresIn: int(ttl / time.Second)} +} + +// writeSessionCookie sets the admin JWT cookie scoped to the admin prefix. +// Max-Age is the refresh window, because refresh accepts an expired access +// token until iat plus refresh_ttl. +func (s *service) writeSessionCookie(w http.ResponseWriter, token string) { + http.SetCookie(w, &http.Cookie{ + Name: AdminCookieName, + Value: token, + Path: s.adminPrefix(), + MaxAge: int(s.refreshTTL / time.Second), + HttpOnly: true, + Secure: true, + SameSite: http.SameSiteStrictMode, + }) +} + func (s *service) refresh(w http.ResponseWriter, r *http.Request) { raw := bearerToken(r) if raw == "" { @@ -247,23 +274,19 @@ func (s *service) me(w http.ResponseWriter, r *http.Request) { WriteData(w, http.StatusOK, profileOf(user), map[string]any{}) } -func profileOf(user BackendUser) map[string]any { - data := map[string]any{ - "id": user.ID, - "login": user.Login, - "email": user.Email, - "first_name": user.FirstName, - "last_name": user.LastName, - "is_superuser": user.IsSuperuser, +func profileOf(user BackendUser) AdminProfile { + profile := AdminProfile{ + ID: user.ID, + Login: user.Login, + Email: user.Email, + FirstName: user.FirstName, + LastName: user.LastName, + IsSuperuser: user.IsSuperuser, } if user.Role.ID != 0 { - data["role"] = map[string]any{ - "id": user.Role.ID, - "code": user.Role.Code, - "name": user.Role.Name, - } + profile.Role = &AdminRoleSummary{ID: user.Role.ID, Code: user.Role.Code, Name: user.Role.Name} } - return data + return profile } func bearerToken(r *http.Request) string { @@ -402,15 +425,18 @@ func adminLoginWindow(app *backpack.App) (int, int) { return maxAttempts, decayMinutes } -func adminIssuer(app *backpack.App) string { +// adminIssuer is app.url plus the admin API login path. JWT verification does +// not check iss, so tokens minted under an earlier prefix stay valid until +// they expire. +func adminIssuer(app *backpack.App, prefix string) string { base := "" if app != nil && app.Config != nil { base = strings.TrimRight(strings.TrimSpace(app.Config.String("app.url")), "/") } - if base == "" { - return "/_admin/api/v1/auth/login" + if prefix == "" { + prefix = DefaultAdminPrefix } - return base + "/_admin/api/v1/auth/login" + return base + prefix + adminAPIVersion + "/auth/login" } // dummyPasswordHash keeps a missing-user login on the bcrypt path. diff --git a/cabana/auth_test.go b/cabana/auth_test.go index 69ad6b6..043c12b 100644 --- a/cabana/auth_test.go +++ b/cabana/auth_test.go @@ -42,7 +42,7 @@ func TestAdminAuthLifecycle(t *testing.T) { gdb := adminGorm(t) h := adminHandler(t, gdb, nil) user := insertAdmin(t, gdb, "life", "Life@Example.Test", adminTestPassword, true, false) - login := postJSON(t, h, "/_admin/api/v1/auth/login", map[string]string{ + login := postJSON(t, h, adminAPI("/auth/login"), map[string]string{ "login": "life", "password": adminTestPassword, }) @@ -63,7 +63,7 @@ func TestAdminAuthLifecycle(t *testing.T) { if !stamped.Valid { t.Fatal("successful login did not stamp last_login") } - byEmail := postJSON(t, h, "/_admin/api/v1/auth/login", map[string]string{ + byEmail := postJSON(t, h, adminAPI("/auth/login"), map[string]string{ "email": "life@example.test", "password": adminTestPassword, }) @@ -71,12 +71,12 @@ func TestAdminAuthLifecycle(t *testing.T) { t.Fatalf("email login status=%d body=%s", byEmail.Code, byEmail.Body.String()) } emailToken := accessToken(t, byEmail.Body.Bytes()) - me := getAuth(t, h, "/_admin/api/v1/auth/me", emailToken) + me := getAuth(t, h, adminAPI("/auth/me"), emailToken) if me.Code != http.StatusOK { t.Fatalf("me status=%d body=%s", me.Code, me.Body.String()) } assertSafeProfile(t, me.Body.Bytes(), user) - refreshed := postAuth(t, h, http.MethodPost, "/_admin/api/v1/auth/refresh", emailToken, nil) + refreshed := postAuth(t, h, http.MethodPost, adminAPI("/auth/refresh"), emailToken, nil) if refreshed.Code != http.StatusOK { t.Fatalf("refresh status=%d body=%s", refreshed.Code, refreshed.Body.String()) } @@ -87,7 +87,7 @@ func TestAdminAuthLifecycle(t *testing.T) { if jwtAudience(t, next) != "backend" { t.Fatal("refreshed token lost the backend audience") } - oldMe := getAuth(t, h, "/_admin/api/v1/auth/me", emailToken) + oldMe := getAuth(t, h, adminAPI("/auth/me"), emailToken) if oldMe.Code != http.StatusUnauthorized { t.Fatalf("previous token after refresh status=%d body=%s", oldMe.Code, oldMe.Body.String()) } @@ -98,14 +98,14 @@ func TestAdminAuthLifecycle(t *testing.T) { if blacklisted != 1 { t.Fatalf("previous jti blacklist rows=%d", blacklisted) } - out := postAuth(t, h, http.MethodPost, "/_admin/api/v1/auth/logout", next, nil) + out := postAuth(t, h, http.MethodPost, adminAPI("/auth/logout"), next, nil) if out.Code != http.StatusOK { t.Fatalf("logout status=%d body=%s", out.Code, out.Body.String()) } if strings.Contains(out.Body.String(), next) { t.Fatal("logout body contains the token") } - after := getAuth(t, h, "/_admin/api/v1/auth/me", next) + after := getAuth(t, h, adminAPI("/auth/me"), next) if after.Code != http.StatusUnauthorized { t.Fatalf("me after logout status=%d", after.Code) } @@ -113,7 +113,7 @@ func TestAdminAuthLifecycle(t *testing.T) { if err := gdb.Model(&cabana.BackendUser{}).Where("id = ?", user.ID).Update("tokens_valid_after", cutoff).Error; err != nil { t.Fatal(err) } - fresh := accessToken(t, postJSON(t, h, "/_admin/api/v1/auth/login", map[string]string{ + fresh := accessToken(t, postJSON(t, h, adminAPI("/auth/login"), map[string]string{ "login": "life", "password": adminTestPassword, }).Body.Bytes()) // Login mints after the cutoff, so this token is current. Move the cutoff @@ -121,7 +121,7 @@ func TestAdminAuthLifecycle(t *testing.T) { if err := gdb.Model(&cabana.BackendUser{}).Where("id = ?", user.ID).Update("tokens_valid_after", time.Now().Add(time.Hour)).Error; err != nil { t.Fatal(err) } - stale := getAuth(t, h, "/_admin/api/v1/auth/me", fresh) + stale := getAuth(t, h, adminAPI("/auth/me"), fresh) if stale.Code != http.StatusUnauthorized { t.Fatalf("stale principal status=%d body=%s", stale.Code, stale.Body.String()) } @@ -131,9 +131,9 @@ func TestAdminInactive(t *testing.T) { gdb := adminGorm(t) h := adminHandler(t, gdb, nil) insertAdmin(t, gdb, "inactive", "inactive@example.test", adminTestPassword, false, false) - unknown := postJSON(t, h, "/_admin/api/v1/auth/login", map[string]string{"login": "nobody", "password": adminTestPassword}) - wrong := postJSON(t, h, "/_admin/api/v1/auth/login", map[string]string{"login": "inactive", "password": "wrong-password"}) - right := postJSON(t, h, "/_admin/api/v1/auth/login", map[string]string{"login": "inactive", "password": adminTestPassword}) + unknown := postJSON(t, h, adminAPI("/auth/login"), map[string]string{"login": "nobody", "password": adminTestPassword}) + wrong := postJSON(t, h, adminAPI("/auth/login"), map[string]string{"login": "inactive", "password": "wrong-password"}) + right := postJSON(t, h, adminAPI("/auth/login"), map[string]string{"login": "inactive", "password": adminTestPassword}) assertSameOpaque(t, unknown, wrong, right) var stamped sql.NullTime if err := gdb.Raw(`SELECT last_login FROM backend_users WHERE login = 'inactive'`).Scan(&stamped).Error; err != nil { @@ -148,8 +148,8 @@ func TestAdminDeleted(t *testing.T) { gdb := adminGorm(t) h := adminHandler(t, gdb, nil) insertAdmin(t, gdb, "deleted", "deleted@example.test", adminTestPassword, true, true) - unknown := postJSON(t, h, "/_admin/api/v1/auth/login", map[string]string{"login": "nobody-else", "password": adminTestPassword}) - deleted := postJSON(t, h, "/_admin/api/v1/auth/login", map[string]string{"login": "deleted", "password": adminTestPassword}) + unknown := postJSON(t, h, adminAPI("/auth/login"), map[string]string{"login": "nobody-else", "password": adminTestPassword}) + deleted := postJSON(t, h, adminAPI("/auth/login"), map[string]string{"login": "deleted", "password": adminTestPassword}) assertSameOpaque(t, unknown, deleted) if strings.Contains(strings.ToLower(deleted.Body.String()), "delet") { t.Fatalf("deleted login disclosed the account: %s", deleted.Body.String()) @@ -160,14 +160,14 @@ func TestAdminBlacklist(t *testing.T) { gdb := adminGorm(t) h := adminHandler(t, gdb, nil) insertAdmin(t, gdb, "revoke", "revoke@example.test", adminTestPassword, true, false) - token := accessToken(t, postJSON(t, h, "/_admin/api/v1/auth/login", map[string]string{ + token := accessToken(t, postJSON(t, h, adminAPI("/auth/login"), map[string]string{ "login": "revoke", "password": adminTestPassword, }).Body.Bytes()) - out := postAuth(t, h, http.MethodPost, "/_admin/api/v1/auth/logout", token, nil) + out := postAuth(t, h, http.MethodPost, adminAPI("/auth/logout"), token, nil) if out.Code != http.StatusOK { t.Fatalf("logout status=%d body=%s", out.Code, out.Body.String()) } - again := postAuth(t, h, http.MethodPost, "/_admin/api/v1/auth/refresh", token, nil) + again := postAuth(t, h, http.MethodPost, adminAPI("/auth/refresh"), token, nil) if again.Code != http.StatusUnauthorized { t.Fatalf("refresh after logout status=%d body=%s", again.Code, again.Body.String()) } @@ -192,7 +192,7 @@ func TestAdminLoginThrottle(t *testing.T) { }) var last *httptest.ResponseRecorder for i := 0; i < 3; i++ { - last = postJSON(t, h, "/_admin/api/v1/auth/login", map[string]string{ + last = postJSON(t, h, adminAPI("/auth/login"), map[string]string{ "login": "throttle-user", "password": adminTestPassword, }) } @@ -217,16 +217,16 @@ func TestAdminAuthLogging(t *testing.T) { slog.SetDefault(slog.New(slog.NewJSONHandler(&buf, nil))) t.Cleanup(func() { slog.SetDefault(prev) }) - ok := postJSON(t, h, "/_admin/api/v1/auth/login", map[string]string{"login": "logged", "password": adminTestPassword}) + ok := postJSON(t, h, adminAPI("/auth/login"), map[string]string{"login": "logged", "password": adminTestPassword}) token := accessToken(t, ok.Body.Bytes()) assertLog(t, &buf, "success", user.ID, adminTestPassword, token) buf.Reset() - postJSON(t, h, "/_admin/api/v1/auth/login", map[string]string{"login": "logged", "password": "not-the-password"}) + postJSON(t, h, adminAPI("/auth/login"), map[string]string{"login": "logged", "password": "not-the-password"}) assertLog(t, &buf, "failed", user.ID, "not-the-password", "") buf.Reset() - postJSON(t, h, "/_admin/api/v1/auth/login", map[string]string{"login": "missing-logged", "password": "not-the-password"}) + postJSON(t, h, adminAPI("/auth/login"), map[string]string{"login": "missing-logged", "password": "not-the-password"}) failedUnknown := buf.String() if !strings.Contains(failedUnknown, `"outcome":"failed"`) { t.Fatalf("unknown login log = %s", failedUnknown) @@ -236,7 +236,7 @@ func TestAdminAuthLogging(t *testing.T) { } buf.Reset() - denied := getAuth(t, h, "/_admin/api/v1/acme/demo/widgets", token) + denied := getAuth(t, h, adminAPI("/acme/demo/widgets"), token) if denied.Code != http.StatusForbidden { t.Fatalf("denied status=%d body=%s", denied.Code, denied.Body.String()) } @@ -551,3 +551,9 @@ func jwtClaims(t *testing.T, token string) map[string]any { func itoa(id uint) string { return strconv.FormatUint(uint64(id), 10) } + +// adminAPI mirrors the internal helper in admin_paths_test.go for this +// external test package: a full admin API path under the default prefix. +func adminAPI(rel string) string { + return cabana.DefaultAdminPrefix + "/api/v1" + rel +} diff --git a/cabana/bulk_test.go b/cabana/bulk_test.go index 6db4a61..60e399e 100644 --- a/cabana/bulk_test.go +++ b/cabana/bulk_test.go @@ -18,7 +18,7 @@ import ( func TestBulkDeleteEmpty(t *testing.T) { cap := &captureRouter{} (&service{}).mount(cap) - key := "POST /_admin/api/v1/{vendor}/{plugin}/{controller}/bulk-delete" + key := "POST " + adminAPI("/{vendor}/{plugin}/{controller}/bulk-delete") mw, ok := cap.middleware[key] if !ok || !containsString(mw, "backend") { t.Fatalf("missing %s in %v", key, cap.routes) diff --git a/cabana/commands_test.go b/cabana/commands_test.go index e7b5cb7..dea5406 100644 --- a/cabana/commands_test.go +++ b/cabana/commands_test.go @@ -107,7 +107,7 @@ func TestAdminResetPasswordCommand(t *testing.T) { t.Fatal(err) } guard := bouncer.NewBackendJWTGuard(adminTestSecret, cabana.BackendUsers{DB: gdb}, nil, nil) - req := httptest.NewRequest(http.MethodGet, "/_admin/api/v1/auth/me", nil) + req := httptest.NewRequest(http.MethodGet, adminAPI("/auth/me"), nil) req.Header.Set("Authorization", "Bearer "+token) if _, err := guard.Authenticate(req); err != nil { t.Fatalf("token before reset: %v", err) diff --git a/cabana/crud_lifecycle_test.go b/cabana/crud_lifecycle_test.go index 7fedf49..90dd477 100644 --- a/cabana/crud_lifecycle_test.go +++ b/cabana/crud_lifecycle_test.go @@ -104,10 +104,10 @@ func TestCRUDRecordRoutes(t *testing.T) { cap := &captureRouter{} (&service{}).mount(cap) for _, want := range []string{ - "POST /_admin/api/v1/{vendor}/{plugin}/{controller}", - "GET /_admin/api/v1/{vendor}/{plugin}/{controller}/{id}", - "PUT /_admin/api/v1/{vendor}/{plugin}/{controller}/{id}", - "DELETE /_admin/api/v1/{vendor}/{plugin}/{controller}/{id}", + "POST " + adminAPI("/{vendor}/{plugin}/{controller}"), + "GET " + adminAPI("/{vendor}/{plugin}/{controller}/{id}"), + "PUT " + adminAPI("/{vendor}/{plugin}/{controller}/{id}"), + "DELETE " + adminAPI("/{vendor}/{plugin}/{controller}/{id}"), } { mw, ok := cap.middleware[want] if !ok { diff --git a/cabana/csrf.go b/cabana/csrf.go new file mode 100644 index 0000000..f751085 --- /dev/null +++ b/cabana/csrf.go @@ -0,0 +1,45 @@ +package cabana + +import ( + "net/http" + "strings" +) + +const ( + // requestedWithHeader is the custom header the admin SPA sends on every + // request. A cross-site form or navigation cannot set it, and a + // cross-origin fetch that sets it needs a CORS preflight the admin API + // never answers (D-19). + requestedWithHeader = "X-Requested-With" + requestedWithAjax = "XMLHttpRequest" +) + +// requireAjax refuses a state-changing admin request that is not +// Bearer-authenticated and does not carry X-Requested-With: XMLHttpRequest. +// It runs before the wrapped handler, so a refused request is never decoded, +// never looks up a controller and never reaches the database. The response +// uses the fixed D-10 code forbidden. +func requireAjax(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !csrfSafe(r) { + WriteError(w, http.StatusForbidden, "forbidden", msgForbidden) + return + } + next(w, r) + } +} + +func csrfSafe(r *http.Request) bool { + switch r.Method { + case http.MethodGet, http.MethodHead, http.MethodOptions: + return true + } + if bearerToken(r) != "" { + return true + } + return isAjax(r) +} + +func isAjax(r *http.Request) bool { + return strings.TrimSpace(r.Header.Get(requestedWithHeader)) == requestedWithAjax +} diff --git a/cabana/http.go b/cabana/http.go index e319d97..c5044eb 100644 --- a/cabana/http.go +++ b/cabana/http.go @@ -13,6 +13,7 @@ import ( "time" "git.golem15.com/golem15/summercms/backpack" + "git.golem15.com/golem15/summercms/boardwalk" "git.golem15.com/golem15/summercms/bouncer" "git.golem15.com/golem15/summercms/pact" "git.golem15.com/golem15/summercms/party" @@ -20,10 +21,12 @@ import ( "gorm.io/gorm" ) -// Routes is the raw admin API mounted by surf.BuildRouter. +// Routes is the raw admin API and SPA mounted by surf.BuildRouter. Prefix is +// the normalized backend.uri every admin route lives under. type Routes struct { Middleware pact.Middleware Mount func(r pact.Router) + Prefix string } type service struct { @@ -38,6 +41,21 @@ type service struct { loginDecay int issuer string bl bouncer.BlacklistStore + prefix string + spa http.Handler +} + +// adminPrefix returns the mount path; a zero service uses the default. +func (s *service) adminPrefix() string { + if s == nil || s.prefix == "" { + return DefaultAdminPrefix + } + return s.prefix +} + +// apiBase is the admin API root: the prefix plus /api/v1 (D-03). +func (s *service) apiBase() string { + return s.adminPrefix() + adminAPIVersion } // Activate compiles admin controllers and, when any exist, requires @@ -54,6 +72,10 @@ func Activate(app *backpack.App, plugins []party.Plugin) (*Routes, error) { if err != nil { return nil, err } + prefix, err := AdminPrefix(app) + if err != nil { + return nil, err + } reg, err := compileRegistry(items) if err != nil { return nil, err @@ -72,7 +94,7 @@ func Activate(app *backpack.App, plugins []party.Plugin) (*Routes, error) { } } bl := adminBlacklist(app) - guard := bouncer.NewBackendJWTGuard(secret, lazyBackendUsers{app: app, reg: reg}, bl, writeUnauthenticated) + guard := bouncer.NewBackendJWTGuard(secret, lazyBackendUsers{app: app, reg: reg}, bl, writeUnauthenticated, AdminCookieName) if _, err := guards.Middleware("backend"); err != nil { if err := guards.Register("summercms.cabana", "backend", guard); err != nil { return nil, err @@ -93,10 +115,31 @@ func Activate(app *backpack.App, plugins []party.Plugin) (*Routes, error) { bcryptCost: adminBcryptCost(app), loginMax: loginMax, loginDecay: loginDecay, - issuer: adminIssuer(app), + issuer: adminIssuer(app, prefix), bl: bl, + prefix: prefix, } - return &Routes{Middleware: mw, Mount: svc.mount}, nil + spa, err := boardwalk.Handler(prefix, http.HandlerFunc(writeNotFound)) + if err != nil { + return nil, fmt.Errorf("cabana: admin SPA: %w", err) + } + svc.spa = spa + return &Routes{Middleware: mw, Mount: svc.mount, Prefix: prefix}, nil +} + +func writeNotFound(w http.ResponseWriter, _ *http.Request) { + WriteError(w, http.StatusNotFound, "not_found", msgNotFound) +} + +// serveSPA answers GET {prefix} and GET {prefix}/{path...} from the embedded +// build. API paths that no route matched fall through to it and receive the +// D-10 not_found envelope, never index.html. +func (s *service) serveSPA(w http.ResponseWriter, r *http.Request) { + if s == nil || s.spa == nil { + writeNotFound(w, r) + return + } + s.spa.ServeHTTP(w, r) } func writeUnauthenticated(w http.ResponseWriter, _ error) { @@ -121,12 +164,15 @@ func (p lazyBackendUsers) FindByID(ctx context.Context, id uint) (*bouncer.Princ func (s *service) mount(r pact.Router) { throttle := fmt.Sprintf("throttle:%d,%d", s.loginMax, s.loginDecay) - r.GroupRaw("/_admin/api/v1/auth", nil, func(g pact.Router) { + api := s.apiBase() + r.GroupRaw(api+"/auth", nil, func(g pact.Router) { + // Login is exempt from the CSRF header: without it the response is a + // Bearer body and no cookie is set, so a cross-site post gains nothing. g.Post("/login", s.login, throttle) - g.Post("/refresh", s.refresh) + g.Post("/refresh", requireAjax(s.refresh)) }) - r.GroupRaw("/_admin/api/v1", []string{"backend"}, func(g pact.Router) { - g.Post("/auth/logout", s.logout) + r.GroupRaw(api, []string{"backend"}, func(g pact.Router) { + g.Post("/auth/logout", requireAjax(s.logout)) g.Get("/auth/me", s.me) g.Get("/navigation", s.navigation) g.Get("/settings", s.settingsList) @@ -134,7 +180,7 @@ func (s *service) mount(r pact.Router) { constrainSetting(g) g.Get("/settings/{code}", s.settingsGet) constrainSetting(g) - g.Put("/settings/{code}", s.settingsPut) + g.Put("/settings/{code}", requireAjax(s.settingsPut)) constrainSetting(g) g.Get("/{vendor}/{plugin}/{controller}/schema/list", s.listSchema) constrainController(g) @@ -144,25 +190,31 @@ func (s *service) mount(r pact.Router) { constrainRelation(g) g.Get("/{vendor}/{plugin}/{controller}", s.list) constrainController(g) - g.Post("/{vendor}/{plugin}/{controller}", s.create) + g.Post("/{vendor}/{plugin}/{controller}", requireAjax(s.create)) constrainController(g) - g.Post("/{vendor}/{plugin}/{controller}/bulk-delete", s.bulkDelete) + g.Post("/{vendor}/{plugin}/{controller}/bulk-delete", requireAjax(s.bulkDelete)) constrainController(g) g.Get("/{vendor}/{plugin}/{controller}/{id}", s.show) constrainController(g) - g.Put("/{vendor}/{plugin}/{controller}/{id}", s.update) + g.Put("/{vendor}/{plugin}/{controller}/{id}", requireAjax(s.update)) constrainController(g) - g.Delete("/{vendor}/{plugin}/{controller}/{id}", s.deleteRecord) + g.Delete("/{vendor}/{plugin}/{controller}/{id}", requireAjax(s.deleteRecord)) constrainController(g) g.Get("/{vendor}/{plugin}/{controller}/{id}/relations/{name}", s.relationLinked) constrainRelation(g) g.Get("/{vendor}/{plugin}/{controller}/{id}/relations/{name}/candidates", s.relationCandidates) constrainRelation(g) - g.Post("/{vendor}/{plugin}/{controller}/{id}/relations/{name}/link", s.relationLink) + g.Post("/{vendor}/{plugin}/{controller}/{id}/relations/{name}/link", requireAjax(s.relationLink)) constrainRelation(g) - g.Post("/{vendor}/{plugin}/{controller}/{id}/relations/{name}/unlink", s.relationUnlink) + g.Post("/{vendor}/{plugin}/{controller}/{id}/relations/{name}/unlink", requireAjax(s.relationUnlink)) constrainRelation(g) }) + // The SPA shell: public, no guard. ServeMux prefers every API pattern + // above over the {path...} wildcard. + r.GroupRaw(s.adminPrefix(), nil, func(g pact.Router) { + g.Get("", s.serveSPA) + g.Get("/{path...}", s.serveSPA) + }) } func constrainController(g pact.Router) { diff --git a/cabana/phase09_contract_test.go b/cabana/phase09_contract_test.go index 3eef44a..dd565b5 100644 --- a/cabana/phase09_contract_test.go +++ b/cabana/phase09_contract_test.go @@ -9,14 +9,15 @@ import ( "testing" ) -// TestPhase09ContractInventory fails when the committed OpenAPI document -// drops a D-09 route or a protected route's 401 response. +// TestPhase09ContractInventory fails when the committed framework admin +// OpenAPI document (admin/openapi/admin.json, D-15) drops an admin API route +// or a protected route's 401 response. Paths are prefix-relative (D-03). func TestPhase09ContractInventory(t *testing.T) { _, file, _, ok := runtime.Caller(0) if !ok { t.Fatal("caller") } - specPath := filepath.Clean(filepath.Join(filepath.Dir(file), "..", "..", "fonoteka.go", "docs", "openapi.json")) + specPath := filepath.Clean(filepath.Join(filepath.Dir(file), "..", "admin", "openapi", "admin.json")) raw, err := os.ReadFile(specPath) if err != nil { t.Fatalf("read %s: %v", specPath, err) @@ -37,11 +38,16 @@ func TestPhase09ContractInventory(t *testing.T) { t.Fatal("openapi is missing the BackendBearer scheme") } public := map[string]bool{ - "POST /_admin/api/v1/auth/login": true, - "POST /_admin/api/v1/auth/refresh": true, + "POST /auth/login": true, + "POST /auth/refresh": true, } seen := map[string]bool{} + apiRoutes := 0 for _, route := range phase09Routes { + if route.spa { + continue + } + apiRoutes++ method, path, ok := splitRoute(route.key) if !ok { t.Fatalf("bad route key %s", route.key) @@ -76,8 +82,11 @@ func TestPhase09ContractInventory(t *testing.T) { t.Fatalf("%s has no 401 response", key) } } - if len(seen) != len(phase09Routes) { - t.Fatalf("contract routes=%d want %d", len(seen), len(phase09Routes)) + if len(seen) != apiRoutes { + t.Fatalf("contract routes=%d want %d", len(seen), apiRoutes) + } + if len(spec.Paths) != len(pathsOf(phase09Routes)) { + t.Fatalf("openapi lists %d paths, the mounted API has %d", len(spec.Paths), len(pathsOf(phase09Routes))) } } @@ -89,3 +98,20 @@ func splitRoute(key string) (method, path string, ok bool) { } return "", "", false } + +func pathsOf(routes []struct { + key string + public bool + spa bool +}) map[string]bool { + out := map[string]bool{} + for _, route := range routes { + if route.spa { + continue + } + if _, path, ok := splitRoute(route.key); ok { + out[path] = true + } + } + return out +} diff --git a/cabana/prefix.go b/cabana/prefix.go new file mode 100644 index 0000000..29d0c53 --- /dev/null +++ b/cabana/prefix.go @@ -0,0 +1,49 @@ +package cabana + +import ( + "fmt" + "regexp" + "strings" + + "git.golem15.com/golem15/summercms/backpack" +) + +// DefaultAdminPrefix is the admin mount path when backend.uri is unset. +// It matches WinterCMS's backendUri default. +const DefaultAdminPrefix = "/backend" + +// AdminCookieName carries the admin JWT for the embedded SPA (D-19). +const AdminCookieName = "summer_admin" + +// adminAPIVersion is appended to the prefix for every admin API route. +const adminAPIVersion = "/api/v1" + +var adminPrefixPattern = regexp.MustCompile(`^(/[a-z0-9][a-z0-9_-]*)+$`) + +// AdminPrefix reads backend.uri and returns the normalized admin mount path. +// Spaces are trimmed, a leading slash is added and trailing slashes are +// removed; an empty value falls back to DefaultAdminPrefix. Every segment +// must be lowercase letters, digits, '-' or '_' and start with a letter or +// digit, so "/" alone, uppercase, spaces and dot segments are rejected. +func AdminPrefix(app *backpack.App) (string, error) { + raw := "" + if app != nil && app.Config != nil { + raw = app.Config.String("backend.uri") + } + return normalizeAdminPrefix(raw) +} + +func normalizeAdminPrefix(raw string) (string, error) { + value := strings.TrimSpace(raw) + if value == "" { + return DefaultAdminPrefix, nil + } + if !strings.HasPrefix(value, "/") { + value = "/" + value + } + value = strings.TrimRight(value, "/") + if value == "" || !adminPrefixPattern.MatchString(value) { + return "", fmt.Errorf("cabana: backend.uri %q is invalid: use one or more lowercase path segments such as /backend (set SUMMER_BACKEND__URI)", raw) + } + return value, nil +} diff --git a/cabana/security_coverage_test.go b/cabana/security_coverage_test.go index ae01dcc..4edbe64 100644 --- a/cabana/security_coverage_test.go +++ b/cabana/security_coverage_test.go @@ -12,35 +12,49 @@ import ( "git.golem15.com/golem15/summercms/pact" ) -// phase09Routes is the D-09 admin surface mounted by service.mount. -// A handler added outside this set, or a protected handler missing the -// backend guard, fails TestPhase09PermissionMatrix. +// phase09Routes is the admin surface mounted by service.mount. API keys are +// method plus the path relative to {backend.uri}/api/v1 (D-03); spa entries +// are the public SPA shell routes relative to {backend.uri} and are not part +// of the OpenAPI inventory. A handler added outside this set, or a protected +// handler missing the backend guard, fails TestPhase09PermissionMatrix. var phase09Routes = []struct { key string public bool + spa bool }{ - {"POST /_admin/api/v1/auth/login", true}, - {"POST /_admin/api/v1/auth/refresh", true}, - {"POST /_admin/api/v1/auth/logout", false}, - {"GET /_admin/api/v1/auth/me", false}, - {"GET /_admin/api/v1/navigation", false}, - {"GET /_admin/api/v1/settings", false}, - {"GET /_admin/api/v1/settings/{code}/schema", false}, - {"GET /_admin/api/v1/settings/{code}", false}, - {"PUT /_admin/api/v1/settings/{code}", false}, - {"GET /_admin/api/v1/{vendor}/{plugin}/{controller}/schema/list", false}, - {"GET /_admin/api/v1/{vendor}/{plugin}/{controller}/schema/form", false}, - {"GET /_admin/api/v1/{vendor}/{plugin}/{controller}/schema/relation/{name}", false}, - {"GET /_admin/api/v1/{vendor}/{plugin}/{controller}", false}, - {"POST /_admin/api/v1/{vendor}/{plugin}/{controller}", false}, - {"POST /_admin/api/v1/{vendor}/{plugin}/{controller}/bulk-delete", false}, - {"GET /_admin/api/v1/{vendor}/{plugin}/{controller}/{id}", false}, - {"PUT /_admin/api/v1/{vendor}/{plugin}/{controller}/{id}", false}, - {"DELETE /_admin/api/v1/{vendor}/{plugin}/{controller}/{id}", false}, - {"GET /_admin/api/v1/{vendor}/{plugin}/{controller}/{id}/relations/{name}", false}, - {"GET /_admin/api/v1/{vendor}/{plugin}/{controller}/{id}/relations/{name}/candidates", false}, - {"POST /_admin/api/v1/{vendor}/{plugin}/{controller}/{id}/relations/{name}/link", false}, - {"POST /_admin/api/v1/{vendor}/{plugin}/{controller}/{id}/relations/{name}/unlink", false}, + {"POST /auth/login", true, false}, + {"POST /auth/refresh", true, false}, + {"POST /auth/logout", false, false}, + {"GET /auth/me", false, false}, + {"GET /navigation", false, false}, + {"GET /settings", false, false}, + {"GET /settings/{code}/schema", false, false}, + {"GET /settings/{code}", false, false}, + {"PUT /settings/{code}", false, false}, + {"GET /{vendor}/{plugin}/{controller}/schema/list", false, false}, + {"GET /{vendor}/{plugin}/{controller}/schema/form", false, false}, + {"GET /{vendor}/{plugin}/{controller}/schema/relation/{name}", false, false}, + {"GET /{vendor}/{plugin}/{controller}", false, false}, + {"POST /{vendor}/{plugin}/{controller}", false, false}, + {"POST /{vendor}/{plugin}/{controller}/bulk-delete", false, false}, + {"GET /{vendor}/{plugin}/{controller}/{id}", false, false}, + {"PUT /{vendor}/{plugin}/{controller}/{id}", false, false}, + {"DELETE /{vendor}/{plugin}/{controller}/{id}", false, false}, + {"GET /{vendor}/{plugin}/{controller}/{id}/relations/{name}", false, false}, + {"GET /{vendor}/{plugin}/{controller}/{id}/relations/{name}/candidates", false, false}, + {"POST /{vendor}/{plugin}/{controller}/{id}/relations/{name}/link", false, false}, + {"POST /{vendor}/{plugin}/{controller}/{id}/relations/{name}/unlink", false, false}, + {"GET ", true, true}, + {"GET /{path...}", true, true}, +} + +// mountedKey is the full mounted route key for an inventory entry. +func mountedKey(key string, spa bool) string { + method, rel, _ := strings.Cut(key, " ") + if spa { + return method + " " + DefaultAdminPrefix + rel + } + return method + " " + adminAPI(rel) } func TestPhase09PermissionMatrix(t *testing.T) { @@ -57,9 +71,10 @@ func TestPhase09PermissionMatrix(t *testing.T) { t.Fatalf("mounted %d admin routes, want %d: %#v", len(got), len(phase09Routes), router.routes) } for _, route := range phase09Routes { - mw, ok := got[route.key] + key := mountedKey(route.key, route.spa) + mw, ok := got[key] if !ok { - t.Fatalf("missing mounted route %s", route.key) + t.Fatalf("missing mounted route %s in %v", key, router.routes) } hasBackend := false for _, name := range mw { @@ -257,7 +272,7 @@ func phase09DeniedService() *service { } func phase09Request(principal *bouncer.Principal) *http.Request { - req := httptest.NewRequest(http.MethodPost, "/_admin/api/v1/acme/demo/widgets/1/relations/editors/link", strings.NewReader(`{}`)) + req := httptest.NewRequest(http.MethodPost, adminAPI("/acme/demo/widgets/1/relations/editors/link"), strings.NewReader(`{}`)) req.SetPathValue("vendor", "acme") req.SetPathValue("plugin", "demo") req.SetPathValue("controller", "widgets") diff --git a/cabana/security_test.go b/cabana/security_test.go index 37236fd..519aa2c 100644 --- a/cabana/security_test.go +++ b/cabana/security_test.go @@ -77,7 +77,7 @@ func TestSecretRedaction(t *testing.T) { } func controllerRequest(principal *bouncer.Principal) *http.Request { - req := httptest.NewRequest(http.MethodGet, "/_admin/api/v1/acme/demo/widgets", nil) + req := httptest.NewRequest(http.MethodGet, adminAPI("/acme/demo/widgets"), nil) req.SetPathValue("vendor", "acme") req.SetPathValue("plugin", "demo") req.SetPathValue("controller", "widgets") diff --git a/go.mod b/go.mod index 96da973..8d753b7 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,9 @@ module git.golem15.com/golem15/summercms go 1.27.0 +// The admin SPA's npm tree may ship .go files; keep ./... out of it. +ignore ./admin/node_modules + require ( github.com/disintegration/imaging v1.6.2 github.com/fsnotify/fsnotify v1.10.1 diff --git a/internal/tools/swagger2openapi/main.go b/internal/tools/swagger2openapi/main.go new file mode 100644 index 0000000..db0c3c9 --- /dev/null +++ b/internal/tools/swagger2openapi/main.go @@ -0,0 +1,380 @@ +// Command swagger2openapi converts swag v1's Swagger 2.0 JSON to OpenAPI 3.0 +// so openapi-typescript 7.x can consume it. swag v1 has no OpenAPI 3 emitter +// (v2 is RC and rejected by STACK.md). It is a copy of the app repository's +// converter plus a rewrite of cabana's opaque JSON types into unions, used by +// scripts/check-admin-openapi.sh to build admin/openapi/admin.json (D-15). +package main + +import ( + "encoding/json" + "fmt" + "os" +) + +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "usage: swagger2openapi \n") + os.Exit(2) + } + raw, err := os.ReadFile(os.Args[1]) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + var doc map[string]any + if err := json.Unmarshal(raw, &doc); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + out := swagger2openapi(doc) + enc, err := json.MarshalIndent(out, "", " ") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + enc = append(enc, '\n') + if _, err := os.Stdout.Write(enc); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func swagger2openapi(doc map[string]any) map[string]any { + out := map[string]any{ + "openapi": "3.0.3", + } + if info, ok := doc["info"]; ok { + out["info"] = info + } + if tags, ok := doc["tags"]; ok { + out["tags"] = tags + } + if servers := convertServers(doc); len(servers) > 0 { + out["servers"] = servers + } + if paths, ok := doc["paths"].(map[string]any); ok { + out["paths"] = convertPaths(paths) + } + components := map[string]any{} + if defs, ok := doc["definitions"].(map[string]any); ok { + rewriteOpaque(defs) + components["schemas"] = defs + } + if sec, ok := doc["securityDefinitions"].(map[string]any); ok { + components["securitySchemes"] = convertSecurity(sec) + } + if len(components) > 0 { + out["components"] = components + } + if sec, ok := doc["security"]; ok { + out["security"] = sec + } + return rewriteRefs(out).(map[string]any) +} + +func convertServers(doc map[string]any) []any { + host, _ := doc["host"].(string) + base, _ := doc["basePath"].(string) + schemes, _ := doc["schemes"].([]any) + if host == "" && (base == "" || base == "/") && len(schemes) == 0 { + return nil + } + if len(schemes) == 0 { + schemes = []any{"https"} + } + if base == "" { + base = "/" + } + out := make([]any, 0, len(schemes)) + for _, s := range schemes { + scheme, _ := s.(string) + url := scheme + "://" + host + if host == "" { + url = base + } else if base != "/" { + url += base + } + out = append(out, map[string]any{"url": url}) + } + return out +} + +func convertPaths(paths map[string]any) map[string]any { + out := make(map[string]any, len(paths)) + for path, raw := range paths { + item, ok := raw.(map[string]any) + if !ok { + out[path] = raw + continue + } + converted := make(map[string]any, len(item)) + var produces []string + if p, ok := item["produces"].([]any); ok { + produces = stringList(p) + } + var consumes []string + if c, ok := item["consumes"].([]any); ok { + consumes = stringList(c) + } + for k, v := range item { + switch k { + case "produces", "consumes": + continue + case "get", "put", "post", "delete", "options", "head", "patch", "trace": + op, ok := v.(map[string]any) + if !ok { + converted[k] = v + continue + } + converted[k] = convertOperation(op, produces, consumes) + default: + converted[k] = v + } + } + out[path] = converted + } + return out +} + +func convertOperation(op map[string]any, parentProduces, parentConsumes []string) map[string]any { + out := make(map[string]any, len(op)) + produces := parentProduces + if p, ok := op["produces"].([]any); ok { + produces = stringList(p) + } + if len(produces) == 0 { + produces = []string{"application/json"} + } + consumes := parentConsumes + if c, ok := op["consumes"].([]any); ok { + consumes = stringList(c) + } + if len(consumes) == 0 { + consumes = []string{"application/json"} + } + for k, v := range op { + switch k { + case "produces", "consumes": + continue + case "parameters": + params, body := splitParameters(v, consumes) + if len(params) > 0 { + out["parameters"] = params + } + if body != nil { + out["requestBody"] = body + } + case "responses": + res, ok := v.(map[string]any) + if !ok { + out[k] = v + continue + } + out[k] = convertResponses(res, produces) + default: + out[k] = v + } + } + return out +} + +func splitParameters(v any, consumes []string) (params []any, body map[string]any) { + list, ok := v.([]any) + if !ok { + return nil, nil + } + for _, item := range list { + p, ok := item.(map[string]any) + if !ok { + params = append(params, item) + continue + } + if in, _ := p["in"].(string); in == "body" { + content := map[string]any{} + for _, ct := range consumes { + entry := map[string]any{} + if schema, ok := p["schema"]; ok { + entry["schema"] = schema + } + content[ct] = entry + } + body = map[string]any{"content": content} + if req, ok := p["required"]; ok { + body["required"] = req + } + if desc, ok := p["description"]; ok { + body["description"] = desc + } + continue + } + params = append(params, convertParameter(p)) + } + return params, body +} + +var paramSchemaKeys = map[string]struct{}{ + "type": {}, "format": {}, "items": {}, "enum": {}, "default": {}, + "minimum": {}, "maximum": {}, "minLength": {}, "maxLength": {}, + "pattern": {}, "uniqueItems": {}, "multipleOf": {}, + "exclusiveMinimum": {}, "exclusiveMaximum": {}, + "additionalProperties": {}, "properties": {}, +} + +func convertParameter(p map[string]any) map[string]any { + out := make(map[string]any, len(p)) + schema := map[string]any{} + for k, v := range p { + if _, ok := paramSchemaKeys[k]; ok { + schema[k] = v + continue + } + out[k] = v + } + if len(schema) > 0 { + out["schema"] = schema + } + return out +} + +func convertResponses(res map[string]any, produces []string) map[string]any { + out := make(map[string]any, len(res)) + for code, raw := range res { + r, ok := raw.(map[string]any) + if !ok { + out[code] = raw + continue + } + converted := make(map[string]any, len(r)) + var schema any + for k, v := range r { + if k == "schema" { + schema = v + continue + } + converted[k] = v + } + if schema != nil { + content := map[string]any{} + for _, ct := range produces { + content[ct] = map[string]any{"schema": schema} + } + converted["content"] = content + } + out[code] = converted + } + return out +} + +func convertSecurity(sec map[string]any) map[string]any { + out := make(map[string]any, len(sec)) + for name, raw := range sec { + s, ok := raw.(map[string]any) + if !ok { + out[name] = raw + continue + } + copied := make(map[string]any, len(s)) + for k, v := range s { + copied[k] = v + } + if t, _ := copied["type"].(string); t == "oauth2" { + flows := map[string]any{} + flow, _ := copied["flow"].(string) + delete(copied, "flow") + flowObj := map[string]any{} + if u, ok := copied["authorizationUrl"]; ok { + flowObj["authorizationUrl"] = u + delete(copied, "authorizationUrl") + } + if u, ok := copied["tokenUrl"]; ok { + flowObj["tokenUrl"] = u + delete(copied, "tokenUrl") + } + if sc, ok := copied["scopes"]; ok { + flowObj["scopes"] = sc + delete(copied, "scopes") + } + switch flow { + case "implicit": + flows["implicit"] = flowObj + case "password": + flows["password"] = flowObj + case "application": + flows["clientCredentials"] = flowObj + case "accessCode": + flows["authorizationCode"] = flowObj + } + copied["flows"] = flows + } + out[name] = copied + } + return out +} + +func rewriteRefs(v any) any { + switch t := v.(type) { + case map[string]any: + out := make(map[string]any, len(t)) + for k, val := range t { + if k == "$ref" { + if s, ok := val.(string); ok { + const from = "#/definitions/" + const to = "#/components/schemas/" + if len(s) >= len(from) && s[:len(from)] == from { + out[k] = to + s[len(from):] + continue + } + } + } + out[k] = rewriteRefs(val) + } + return out + case []any: + out := make([]any, len(t)) + for i, val := range t { + out[i] = rewriteRefs(val) + } + return out + default: + return v + } +} + +func stringList(in []any) []string { + out := make([]string, 0, len(in)) + for _, v := range in { + s, ok := v.(string) + if ok && s != "" { + out = append(out, s) + } + } + return out +} + +// opaqueUnions replaces definitions that swag can only see as an empty object +// because their Go type marshals itself. cabana.jsonScalar is a string, +// number, boolean or null; cabana.fieldContext is a string or string list. +var opaqueUnions = map[string]map[string]any{ + "cabana.jsonScalar": { + "nullable": true, + "oneOf": []any{ + map[string]any{"type": "string"}, + map[string]any{"type": "number"}, + map[string]any{"type": "boolean"}, + }, + }, + "cabana.fieldContext": { + "oneOf": []any{ + map[string]any{"type": "string"}, + map[string]any{"type": "array", "items": map[string]any{"type": "string"}}, + }, + }, +} + +func rewriteOpaque(defs map[string]any) { + for name, union := range opaqueUnions { + if _, ok := defs[name]; ok { + defs[name] = union + } + } +} diff --git a/scripts/check-admin-openapi.sh b/scripts/check-admin-openapi.sh new file mode 100755 index 0000000..77a52ee --- /dev/null +++ b/scripts/check-admin-openapi.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# Generate the framework admin OpenAPI document and the admin SPA's TypeScript +# types from the swag annotations in cabana (D-15): +# swag v1.16.6 (Swagger 2.0) -> internal/tools/swagger2openapi (OpenAPI 3.0) +# -> openapi-typescript (admin devDependency) -> admin/src/api/schema.d.ts +# Without arguments the outputs replace admin/openapi/admin.json and +# admin/src/api/schema.d.ts. With --check nothing is written and the script +# exits non-zero when either committed file differs from a fresh generation. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +CHECK=0 +case "${1:-}" in + "") ;; + --check) CHECK=1 ;; + *) + echo "usage: scripts/check-admin-openapi.sh [--check]" >&2 + exit 2 + ;; +esac + +if [[ ! -d admin/node_modules ]]; then + npm --prefix admin ci +fi + +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +go run github.com/swaggo/swag/cmd/swag@v1.16.6 init \ + --dir cabana \ + --generalInfo admin_openapi.go \ + --output "$TMP" \ + --outputTypes json \ + --requiredByDefault \ + --quiet + +if [[ ! -s "$TMP/swagger.json" ]]; then + echo "check-admin-openapi: swag did not generate swagger.json" >&2 + exit 1 +fi + +go run ./internal/tools/swagger2openapi "$TMP/swagger.json" > "$TMP/admin.json" +admin/node_modules/.bin/openapi-typescript "$TMP/admin.json" -o "$TMP/schema.d.ts" + +if [[ "$CHECK" -eq 1 ]]; then + status=0 + diff -u admin/openapi/admin.json "$TMP/admin.json" || status=1 + diff -u admin/src/api/schema.d.ts "$TMP/schema.d.ts" || status=1 + if [[ "$status" -ne 0 ]]; then + echo "check-admin-openapi: committed admin OpenAPI output is stale; run scripts/check-admin-openapi.sh" >&2 + fi + exit "$status" +fi + +mkdir -p admin/openapi admin/src/api +cp "$TMP/admin.json" admin/openapi/admin.json +cp "$TMP/schema.d.ts" admin/src/api/schema.d.ts +echo "check-admin-openapi: wrote admin/openapi/admin.json and admin/src/api/schema.d.ts"