diff --git a/.github/actions/publish-website/action.yml b/.github/actions/publish-website/action.yml index 116e2716b..23670c658 100644 --- a/.github/actions/publish-website/action.yml +++ b/.github/actions/publish-website/action.yml @@ -1,6 +1,9 @@ name: "Publish website" description: "Publish website to web hosting" inputs: + image-name: + required: true + description: Mokapi image name username: required: true description: ftp username @@ -18,7 +21,7 @@ runs: steps: - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 23.11.1 registry-url: 'https://registry.npmjs.org' - id: release uses: pozetroninc/github-action-get-latest-release@master @@ -26,6 +29,19 @@ runs: repository: marle3003/mokapi excludes: prerelease, draft token: ${{ inputs.token }} + - name: Run mokapi image + run: docker run --name mokapi --rm -d -p 80:80 -p 8080:8080 -p 9092:9092 -p 8389:8389 -p 8025:8025 --mount type=bind,source=$(pwd)/webui/scripts/dashboard-demo/demo-configs,target=/data --env MOKAPI_Providers_File_Directory=/data ${{ inputs.image-name }} + shell: bash + - name: Build Demo Dashboard + working-directory: ./webui/scripts/dashboard-demo + run: | + npm install + node ci.ts + shell: bash + - name: Stop Mokapi + if: always() + run: docker stop mokapi || true + shell: bash - name: build website working-directory: ./webui run: | diff --git a/.github/actions/run-frontend-tests/action.yml b/.github/actions/run-frontend-tests/action.yml index 7424b8105..0adddeb5c 100644 --- a/.github/actions/run-frontend-tests/action.yml +++ b/.github/actions/run-frontend-tests/action.yml @@ -34,7 +34,7 @@ runs: npm ci npm run copy-docs npm run build-sitemap - npm run build + npm run build-dashboard shell: bash - name: Install Playwright working-directory: ./webui @@ -42,7 +42,7 @@ runs: shell: bash - name: Run your tests working-directory: ./webui - run: npx playwright test + run: npx playwright test --project=dashboard shell: bash - name: Upload test results if: always() @@ -59,4 +59,8 @@ runs: uses: actions/upload-artifact@v4 with: name: mokapi-test-logs - path: /var/tmp/mokapi.log \ No newline at end of file + path: /var/tmp/mokapi.log + - name: Stop Mokapi + if: always() + run: docker stop mokapi || true + shell: bash \ No newline at end of file diff --git a/.github/workflows/alpha.yml b/.github/workflows/alpha.yml index ef219a8f7..7166c8ed5 100644 --- a/.github/workflows/alpha.yml +++ b/.github/workflows/alpha.yml @@ -74,7 +74,7 @@ jobs: name: Publish website runs-on: ubuntu-latest if: "success()" - needs: [ build-alpha ] + needs: [ setup, build-alpha ] steps: - name: Check out code uses: actions/checkout@v4 @@ -84,6 +84,7 @@ jobs: uses: chetan/git-restore-mtime-action@v2 - uses: ./.github/actions/publish-website with: + image-name: ${{ needs.setup.outputs.image-name }} username: ${{ secrets.FTP_USERNAME }} password: ${{ secrets.FTP_PASSWORD }} server: ${{ secrets.FTP_SERVER }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1a9c4fa9d..f02406b48 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.25.1 + go-version: 1.25.5 - name: Check out code uses: actions/checkout@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 20b3e7caa..379123ef6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,7 +26,7 @@ jobs: password: ${{ secrets.DOCKER_PASSWORD }} - uses: actions/setup-go@v5 with: - go-version: 1.25.1 + go-version: 1.25.5 - uses: actions/setup-node@v4 with: node-version: 23 @@ -59,7 +59,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-go@v5 with: - go-version: 1.25.1 + go-version: 1.25.5 - uses: actions/setup-node@v4 with: node-version: 23 diff --git a/Taskfile.yml b/Taskfile.yml index b558c3c90..25e268494 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -33,7 +33,7 @@ tasks: - npm run clean - npm run copy-docs - npm version {{.VERSION}} - - npm run build + - npm run build-dashboard build-npm-package: deps: [build-vue-app] cmds: diff --git a/api/handler_http.go b/api/handler_http.go index 1bfbaea21..f6134df5a 100644 --- a/api/handler_http.go +++ b/api/handler_http.go @@ -53,7 +53,7 @@ type param struct { Required bool `json:"required"` Deprecated bool `json:"deprecated"` Style string `json:"style,omitempty"` - Exploded bool `json:"exploded"` + Explode bool `json:"explode"` AllowReserved bool `json:"allowReserved"` Schema *schema.Schema `json:"schema"` } @@ -302,7 +302,7 @@ func getParameters(params openapi.Parameters) (result []param) { Required: p.Value.Required, Deprecated: p.Value.Deprecated, Style: p.Value.Style, - Exploded: p.Value.IsExplode(), + Explode: p.Value.IsExplode(), AllowReserved: p.Value.AllowReserved, Schema: p.Value.Schema, } diff --git a/api/handler_http_test.go b/api/handler_http_test.go index 16805061f..844929963 100644 --- a/api/handler_http_test.go +++ b/api/handler_http_test.go @@ -119,7 +119,7 @@ func TestHandler_Http(t *testing.T) { ) }, requestUrl: "http://foo.api/api/services/http/foo", - responseBody: `{"name":"foo","servers":[{"url":"/","description":""}],"paths":[{"path":"/foo/{bar}","operations":[{"method":"get","deprecated":false,"parameters":[{"name":"bar","type":"path","required":true,"deprecated":false,"exploded":false,"allowReserved":false,"schema":{"type":"string"}}]}]}]`, + responseBody: `{"name":"foo","servers":[{"url":"/","description":""}],"paths":[{"path":"/foo/{bar}","operations":[{"method":"get","deprecated":false,"parameters":[{"name":"bar","type":"path","required":true,"deprecated":false,"explode":false,"allowReserved":false,"schema":{"type":"string"}}]}]}]`, }, { name: "get http service with requestBody", diff --git a/docs/config.json b/docs/config.json index 0233330fa..adc66f4d9 100644 --- a/docs/config.json +++ b/docs/config.json @@ -160,7 +160,7 @@ "component": "examples", "hideNavigation": true, "hideInNavigation": true, - "canonical": "https://mokapi.io/docs/resources", + "canonical": "https://mokapi.io/docs/resources/tutorials", "title": "Explore Mokapi Resources: Tutorials, Examples, and Blog Articles", "description": "Explore Mokapi's resources including tutorials, examples, and blog articles. Learn to mock APIs, validate schemas, and streamline your development." }, @@ -182,7 +182,7 @@ "component": "examples", "hideNavigation": true, "hideInNavigation": true, - "canonical": "https://mokapi.io/docs/resources", + "canonical": "https://mokapi.io/docs/resources/examples", "title": "Explore Mokapi Resources: Tutorials, Examples, and Blog Articles", "description": "Explore Mokapi's resources including tutorials, examples, and blog articles. Learn to mock APIs, validate schemas, and streamline your development." }, @@ -196,7 +196,7 @@ "component": "examples", "hideNavigation": true, "hideInNavigation": true, - "canonical": "https://mokapi.io/docs/resources", + "canonical": "https://mokapi.io/docs/resources/blogs", "title": "Explore Mokapi Resources: Tutorials, Examples, and Blog Articles", "description": "Explore Mokapi's resources including tutorials, examples, and blog articles. Learn to mock APIs, validate schemas, and streamline your development." }, diff --git a/docs/guides/mail/overview.md b/docs/guides/mail/overview.md index 7c01dbeb7..ac0065270 100644 --- a/docs/guides/mail/overview.md +++ b/docs/guides/mail/overview.md @@ -237,11 +237,12 @@ folders: The settings object defines global configuration options that influence server behavior. -| Field Name | Type | Default | Description | -|-------------------|---------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| maxRecipients | integer | 0 | (Optional) Maximum number of recipients per email. Use 0 for unlimited. | -| autoCreateMailbox | boolean | true | (Optional) Allow create mailboxes at runtime. | -| maxInboxMails | integer | 100 | (Optional) Maximum number of messages kept in the INBOX folder. Oldest mails are removed when the limit is exceeded. Use 0 to disable the limit and store messages indefinitely (not recommended). | +| Field Name | Type | Default | Description | +|-----------------------|---------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| maxRecipients | integer | 0 | (Optional) Maximum number of recipients per email. Use 0 for unlimited. | +| autoCreateMailbox | boolean | true | (Optional) Allow create mailboxes at runtime. | +| maxInboxMails | integer | 100 | (Optional) Maximum number of messages kept in the INBOX folder. Oldest mails are removed when the limit is exceeded. Use 0 to disable the limit and store messages indefinitely (not recommended). | +| AllowUnknownSenders | boolean | true | (Optional) If true, the server accepts any MAIL FROM: address, even if it’s not listed in the mailboxes: configuration. | ##### Mailbox Object Example diff --git a/docs/javascript-api/modules.md b/docs/javascript-api/modules.md index aeca7efb5..66e296714 100644 --- a/docs/javascript-api/modules.md +++ b/docs/javascript-api/modules.md @@ -10,9 +10,10 @@ Mokapi supports importing three types of modules: - **Built-in modules**: Provided by Mokapi for various functionalities. - **Local filesystem modules**: Custom scripts and Node.js packages. - **JSON & YAML modules**: Configuration files converted into JavaScript objects. +- **Remote modules**: Hosted on a web server or CDN ``` box=tip -Mokapi monitors all imported modules with `fsnotify`. If a module is modified, any dependent script is automatically reloaded. +Mokapi monitors all imported modules with `fsnotify`. If a module is modified, any dependent script that contains a `default` export is automatically reloaded. ``` ## Built-in Modules @@ -26,7 +27,7 @@ import { fake } from 'mokapi/faker' ## Local Filesystem Modules -You can import files using relative or absolute paths, and Mokapi supports Node.js modules. +Import files using relative or absolute paths. Node.js resolution rules are supported. ```javascript import { someFunc } from './helpers.js' @@ -36,7 +37,7 @@ import dateTime from 'date-time' // Requires: npm install date-time ## JSON & YAML Modules -Mokapi allows importing JSON and YAML files, automatically converting them into JavaScript objects. +JSON and YAML files can be imported and converted automatically to objects: ```javascript tab=Javascript import users from './users.json' @@ -57,4 +58,15 @@ console.log(envs[0]) - production ``` +## Remote Modules + +Modules can also be hosted remotely on public web servers, GitHub, or CDNs. + +```js +import { helper } from 'https://example.com/mokapi-helpers.js' +``` + +- Mokapi uses its [HTTP provider](/docs/configuration/dynamic/http.md) to load remote modules +- This allows dynamic updates without restarting Mokapi + By leveraging these module types, you can create flexible, maintainable, and scalable Mokapi scripts. \ No newline at end of file diff --git a/docs/javascript-api/mokapi/cron.md b/docs/javascript-api/mokapi/cron.md index ee880d86d..7ed5da1a8 100644 --- a/docs/javascript-api/mokapi/cron.md +++ b/docs/javascript-api/mokapi/cron.md @@ -6,9 +6,10 @@ description: Schedules a new periodic job using cron expression. Schedules a new periodic job using cron expression. -``` box=info -By default, the first execution happens immediately, check ScheduledEventArgs -``` +### Behavior + +- By default, **cron jobs wait for the first scheduled tick** before executing. +- To run the job immediately on creation, set `SkipImmediateFirstRun: false` in `args`. ## Parameters diff --git a/docs/javascript-api/mokapi/eventhandler/scheduledeventargs.md b/docs/javascript-api/mokapi/eventhandler/scheduledeventargs.md index 04c3373d7..8a4635ef0 100644 --- a/docs/javascript-api/mokapi/eventhandler/scheduledeventargs.md +++ b/docs/javascript-api/mokapi/eventhandler/scheduledeventargs.md @@ -7,11 +7,11 @@ description: ScheduledEventArgs is an object used by every and cron function. ScheduledEventArgs is an object used by [every](/docs/javascript-api/mokapi/every.md) and [cron](/docs/javascript-api/mokapi/cron.md) function. -| Name | Type | Description | -|-----------------------|---------|---------------------------------------------------------------------------------------------------------------------------------| -| times | number | Defines the number of times the scheduled function is executed. | -| skipImmediateFirstRun | boolean | Toggles behavior of first execution. If true job does not start immediately but rather wait until the first scheduled interval. | -| tags | object | Adds or overrides existing tags used in dashboard | +| Name | Type | Default | Description | +|-----------------------|----------|---------|---------------------------------------------------------------------------------------------------------------------------------| +| times | number | -1 | How many times the job should execute (-1 for unlimited). | +| skipImmediateFirstRun | boolean | | Toggles behavior of first execution. If true job does not start immediately but rather wait until the first scheduled interval. | +| tags | object | {} | Optional tags for identifying the job. | ## Examples diff --git a/docs/javascript-api/mokapi/every.md b/docs/javascript-api/mokapi/every.md index 26d68e0e6..cf74c90c3 100644 --- a/docs/javascript-api/mokapi/every.md +++ b/docs/javascript-api/mokapi/every.md @@ -8,15 +8,16 @@ Schedules a new periodic job with an interval. Interval string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". -``` box=info -By default, the first execution happens immediately, check ScheduledEventArgs -``` +### Behavior + +- By default, **`every` jobs run immediately**, then repeat according to the interval. +- To skip the immediate run, set `SkipImmediateFirstRun: true` in `args`. | Parameter | Type | Description | |-----------------|----------|----------------------------------------------------------------------------------------------------------------------------------| | interval | string | Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". | | handler | function | The handler function to be executed every `interval`. By default, the first execution happens immediately. | -| args (optional) | object | [ScheduledEventArgs](/docs/javascript-api/mokapi/eventhandler/scheduledeventargs.md) object contains additional event arguments. | +| args (optional) | object | [ScheduledEventArgs](/docs/javascript-api/mokapi/eventhandler/scheduledeventargs.md) object contains additional event arguments. | ## Example diff --git a/docs/javascript-api/overview.md b/docs/javascript-api/overview.md index 955d4cfc9..4c2bd6e50 100644 --- a/docs/javascript-api/overview.md +++ b/docs/javascript-api/overview.md @@ -1,48 +1,155 @@ --- title: JavaScript API Documentation -description: Explore Mokapi’s JavaScript API to extend its functionality. Learn how to use modules for HTTP, Kafka, YAML, Mustache, and more. +description: Extend and customize Mokapi using JavaScript. Learn how to structure scripts, use built-in modules, and handle runtime limitations. --- + # Mokapi JavaScript API -Mokapi provides a powerful JavaScript API that allows you to extend its functionality. -This documentation outlines the available modules and their capabilities. -To learn how Mokapi handles module imports, see [Modules](/docs/javascript-api/modules.md). +Mokapi allows you to extend and customize its behavior using JavaScript. +JavaScript is used to implement dynamic logic such as request handling, data generation, +scheduled jobs, and event-driven workflows. + +Mokapi executes JavaScript in a **simple and explicit way**: +only files that export a **default function** are executed. +All other JavaScript files are treated as **modules**. + +This documentation explains: +- How JavaScript is executed in Mokapi +- Script vs module structure +- Runtime environment limitations +- Built-in modules + +## How JavaScript Is Used in Mokapi + +JavaScript can be used for tasks such as: + +- Handling HTTP, Kafka, and other protocol events +- Generating dynamic mock data +- Scheduling background jobs +- Transforming or enriching request and response data + +Each JavaScript file is either: +- an **executable script**, or +- a **module** imported by other scripts + +## Executable Scripts (Default Function) + +A JavaScript file is executed **only if it exports a default function**. + +```js +export default function (ctx) { + // executed by Mokapi +} +``` + +- The default function is the entry point of the script +- Mokapi invokes this function directly +- All execution logic must be placed inside this function + +If a file does not export a default function, Mokapi **will not execute it**. + +## Runtime Environment + +Mokapi executes JavaScript in its own runtime. +It is **not** a Node.js environment and **not** a browser environment. + +As a result, many APIs that are commonly available in Node.js or browsers are **not available** in Mokapi. + +### ⚠️ Important Limitations + +- Node.js built-ins such as `fs`, `path`, `os`, or `net` are not available +- Browser APIs such as `window`, `document`, or `fetch` are not available +- Third-party packages that rely on Node.js or browser APIs may fail + +### Provided Alternatives + +Mokapi provides its own APIs for common tasks: + +- **HTTP requests** + Use the `fetch` function from `mokapi/http`: + ```js + import { fetch } from "mokapi/http" + ``` +- **File access** + Use the global open() function to read files: + ```js + const text = open("data/example.json") + ``` +- **Environment variables** + Use env() from the core API: + ```js + import { env } from "mokapi" + ``` + +## Modules + +JavaScript files without a **default export** are treated as **modules**. +Modules allow you to organize and reuse code. + +```js +export function normalizeUser(user) { + return { ...user, active: true } +} +``` + +- Modules are not executed by Mokapi +- Can be imported by executable scripts or other modules +- Used to organize shared logic + +For more details on the different types of modules (built-in, local filesystem, JSON/YAML), see the dedicated [JavaScript Modules guide](/docs/javascript-api/modules.md). + +## Module Resolution + +Mokapi resolves imports using the same algorithm as Node.js: + +1. The directory of the importing file +2. Any node_modules directory in the same folder +3. Parent directories up to the nearest package.json + +> Module resolution only determines where Mokapi looks for the file. + Runtime limitations still apply: modules that rely on Node.js or browser APIs may fail. + +## TypeScript Support ``` box=tip url=[@types/mokapi on npm](https://www.npmjs.com/package/@types/mokapi) -Mokapi offers a TypeScript definition package. Install it using: +Mokapi provides TypeScript type definitions for its JavaScript API. +Install them using: `npm install @types/mokapi --save-dev` ``` -## Available Modules +## Built-in Modules + +Mokapi provides a set of built-in JavaScript modules that can be used from executable scripts +and custom modules. ### mokapi (Core API) Provides core functions for scheduling jobs, handling events, and accessing environment variables. -| Functions | Description | -|------------------------------------------------------------------------------|-------------------------------------------------------| -| [cron( expression, handler, \[args\] )](/docs/javascript-api/mokapi/cron.md) | Schedules a new periodic job using a cron expression. | -| [date( \[args\] )](/docs/javascript-api/mokapi/date.md) | Returns a formatted date string. | -| [env( name )](/docs/javascript-api/mokapi/env.md) | Gets the value of an environment variable. | -| [every( interval, handler, \[args\] )](/docs/javascript-api/mokapi/every.md) | Runs a periodic job at a fixed interval. | -| [on( event, handler, \[args\]](/docs/javascript-api/mokapi/on.md) ) | Registers an event handler. | -| [sleep( time )](/docs/javascript-api/mokapi/sleep.md) | Pauses execution for a specified duration. | -| [marshal( value, \[encoding\] )](/docs/javascript-api/mokapi/marshal.md) | Converts a value to a marshaled string. | +| Functions | Description | +|------------------------------------------------------------------------------|---------------------------------------------------| +| [cron( expression, handler, \[args\] )](/docs/javascript-api/mokapi/cron.md) | Schedules a periodic job using a cron expression. | +| [date( \[args\] )](/docs/javascript-api/mokapi/date.md) | Returns a formatted date string. | +| [env( name )](/docs/javascript-api/mokapi/env.md) | Gets the value of an environment variable. | +| [every( interval, handler, \[args\] )](/docs/javascript-api/mokapi/every.md) | Runs a periodic job at a fixed interval. | +| [on( event, handler, \[args\]](/docs/javascript-api/mokapi/on.md) ) | Registers an event handler. | +| [sleep( time )](/docs/javascript-api/mokapi/sleep.md) | Pauses execution | +| [marshal( value, \[encoding\] )](/docs/javascript-api/mokapi/marshal.md) | Converts a value to a marshaled string. | ### mokapi/http (HTTP Requests) Functions to send HTTP requests within Mokapi scripts. -| Functions | Description | -|-----------------------------------------------------------------------------------|----------------------------------------| -| [get( url, \[args\] )](/docs/javascript-api/mokapi-http/get.md) | Sends an HTTP GET request. | -| [post( url, \[body\], \[args\] )](/docs/javascript-api/mokapi-http/post.md) | Sends an HTTP POST request | -| [put( url, \[body\], \[args\] )](/docs/javascript-api/mokapi-http/put.md) | Sends an HTTP PUT request | -| [head( url, \[args\] )](/docs/javascript-api/mokapi-http/head.md) | Sends an HTTP HEAD request | -| [patch( url, \[body\], \[args\] )](/docs/javascript-api/mokapi-http/patch.md) | Sends an HTTP PATCH request | -| [delete( url, \[body\], \[args\] )](/docs/javascript-api/mokapi-http/delete.md) | Sends an HTTP DELETE request | -| [options( url, \[body\], \[args\] )](/docs/javascript-api/mokapi-http/options.md) | Sends an HTTP OPTIONS request | -| [fetch( url, \[opts\] )](/docs/javascript-api/mokapi-http/fetch.md) | Create an HTTP request using Fetch API | +| Functions | Description | +|-----------------------------------------------------------------------------------|-------------------------------| +| [get( url, \[args\] )](/docs/javascript-api/mokapi-http/get.md) | Sends an HTTP GET request. | +| [post( url, \[body\], \[args\] )](/docs/javascript-api/mokapi-http/post.md) | Sends an HTTP POST request | +| [put( url, \[body\], \[args\] )](/docs/javascript-api/mokapi-http/put.md) | Sends an HTTP PUT request | +| [head( url, \[args\] )](/docs/javascript-api/mokapi-http/head.md) | Sends an HTTP HEAD request | +| [patch( url, \[body\], \[args\] )](/docs/javascript-api/mokapi-http/patch.md) | Sends an HTTP PATCH request | +| [delete( url, \[body\], \[args\] )](/docs/javascript-api/mokapi-http/delete.md) | Sends an HTTP DELETE request | +| [options( url, \[body\], \[args\] )](/docs/javascript-api/mokapi-http/options.md) | Sends an HTTP OPTIONS request | +| [fetch( url, \[opts\] )](/docs/javascript-api/mokapi-http/fetch.md) | Fetch using Mokapi's API | ### mokapi/faker (Mock Data Generator) @@ -72,19 +179,19 @@ Processes Mustache templates with dynamic data. Handles YAML data parsing and conversion. -| Functions | Description | -|---------------------------------------------------------------------|-------------------------------------------------| -| [parse( text )](/docs/javascript-api/mokapi-yaml/parse.md) | Parses a YAML string into a JavaScript object. | -| [stringify( value )](/docs/javascript-api/mokapi-yaml/stringify.md) | Converts a JavaScript object into YAML format. | +| Functions | Description | +|---------------------------------------------------------------------|--------------------------------------| +| [parse( text )](/docs/javascript-api/mokapi-yaml/parse.md) | Parses a YAML string into an object. | +| [stringify( value )](/docs/javascript-api/mokapi-yaml/stringify.md) | Converts an object to YAML. | ### mokapi/encoding (Encoding Utilities) Functions for encoding and decoding data. -| Functions | Description | -|---------------------------------------------------------------------------------|-----------------------------------| -| [base64.encode( input )](/docs/javascript-api/mokapi-encoding/base64-encode.md) | Encodes a string using Base64. | -| [base64.decode( input )](/docs/javascript-api/mokapi-encoding/base64-decode.md) | Decodes a Base64-encoded string. | +| Functions | Description | +|---------------------------------------------------------------------------------|-----------------------------| +| [base64.encode( input )](/docs/javascript-api/mokapi-encoding/base64-encode.md) | Encodes a string to Base64. | +| [base64.decode( input )](/docs/javascript-api/mokapi-encoding/base64-decode.md) | Decodes a Base64 string. | diff --git a/engine/engine.go b/engine/engine.go index bd97f2e27..9c0512e2e 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -83,9 +83,12 @@ func (e *Engine) AddScript(evt dynamic.ConfigEvent) error { err := e.run(host) if err != nil { if errors.Is(err, UnsupportedError) { - log.Debugf("script not supported: %v", evt.Config.Info.Url.Path) + log.Debugf( + "skipping script execution: JavaScript runner does not support this file type: %s", + evt.Config.Info.Url, + ) } else { - log.Errorf("error executing script %v: %v", evt.Config.Info.Url, err) + log.Errorf("error executing script %s: %v", evt.Config.Info.Url, err) } } }() diff --git a/engine/scheduler.go b/engine/scheduler.go index 39cb847bc..0dd0f31ef 100644 --- a/engine/scheduler.go +++ b/engine/scheduler.go @@ -1,10 +1,11 @@ package engine import ( - "github.com/go-co-op/gocron" "mokapi/engine/common" "sync" "time" + + "github.com/go-co-op/gocron" ) type Scheduler interface { @@ -54,8 +55,8 @@ func (s *DefaultScheduler) Cron(expr string, handler func(), opt common.JobOptio if opt.Times > 0 { s.scheduler.LimitRunsTo(opt.Times) } - if opt.SkipImmediateFirstRun { - s.scheduler.WaitForSchedule() + if !opt.SkipImmediateFirstRun { + handler() } return s.scheduler.Do(handler) diff --git a/examples/mokapi/dashboard.yml b/examples/mokapi/dashboard.yml index c8abc9304..ca1ac5caf 100644 --- a/examples/mokapi/dashboard.yml +++ b/examples/mokapi/dashboard.yml @@ -141,7 +141,7 @@ paths: schema: $ref: './mail.yml#/components/schemas/MailboxDetails' "404": - description: mail not found + description: mailbox not found /api/services/mail/{name}/mailboxes/{mailbox}/messages: get: summary: Retrieve mailboxes for given mail server @@ -166,6 +166,8 @@ paths: type: array items: $ref: './mail.yml#/components/schemas/SmtpMessageInfo' + "404": + description: mailbox not found /api/services/mail/messages/{messageId}: get: summary: Retrieve the mail with the given messageId diff --git a/examples/mokapi/http.yml b/examples/mokapi/http.yml index 6df648b49..833886050 100644 --- a/examples/mokapi/http.yml +++ b/examples/mokapi/http.yml @@ -122,8 +122,11 @@ components: type: string explode: type: boolean + allowReserved: + type: boolean schema: $ref: 'schema.yml#/components/schemas/Schema' + required: [ name, type, required, deprecated, explode, allowReserved ] Header: type: object properties: diff --git a/examples/mokapi/kafka.js b/examples/mokapi/kafka.js index 257b320d3..61d7f7070 100644 --- a/examples/mokapi/kafka.js +++ b/examples/mokapi/kafka.js @@ -257,7 +257,7 @@ export let clusters = [ export let events = [ { - id: '123456', + id: '5d549ffe-97cd-477d-8db6-27ed4963c08d', traits: { namespace: 'kafka', name: 'Kafka World', @@ -292,7 +292,7 @@ export let events = [ } }, { - id: '323456', + id: '670ce171-808c-4f8e-a7cd-209b008aada5', traits: { namespace: 'kafka', name: 'Kafka World', @@ -327,7 +327,7 @@ export let events = [ } }, { - id: '123602', + id: 'cff487df-1fe0-4e1b-b0f6-e599bc61283c', traits: { namespace: 'kafka', name: 'Kafka World', diff --git a/examples/mokapi/ldap.js b/examples/mokapi/ldap.js index 33cea3fad..8f259b511 100644 --- a/examples/mokapi/ldap.js +++ b/examples/mokapi/ldap.js @@ -13,7 +13,7 @@ export let server = [ export const searches = [ { - id: "dkads-23124", + id: "a1289b9b-aff7-4c53-92a0-808c7ce7d907", traits: { namespace: "ldap", name: "LDAP Testserver" @@ -56,7 +56,7 @@ export const searches = [ } }, { - id: "dkads-10004", + id: "fbe9ade9-2adc-451b-a86b-a900c06c0058", traits: { namespace: "ldap", name: "LDAP Testserver" @@ -83,7 +83,7 @@ export const searches = [ } }, { - id: "dkads-222", + id: "762b4a9e-7f26-4314-8ffd-7ae1922ab330", traits: { namespace: "ldap", name: "LDAP Testserver" @@ -107,7 +107,7 @@ export const searches = [ } }, { - id: "dwow-12", + id: "23636b50-e19b-4acc-afc0-f5918a7d2e64", traits: { namespace: "ldap", name: "LDAP Testserver" @@ -126,7 +126,7 @@ export const searches = [ } }, { - id: "abc-12", + id: "2fa8df80-9b15-427b-9c6d-0503aec06ed3", traits: { namespace: "ldap", name: "LDAP Testserver" @@ -147,7 +147,7 @@ export const searches = [ } }, { - id: "fpp-12", + id: "34cbe69d-948c-43e3-aa19-df83c09116c4", traits: { namespace: "ldap", name: "LDAP Testserver" diff --git a/examples/mokapi/mail.js b/examples/mokapi/mail.js index af47796cd..9a9d15d5c 100644 --- a/examples/mokapi/mail.js +++ b/examples/mokapi/mail.js @@ -104,7 +104,7 @@ export let services = [ export let mailEvents = [ { - id: "8832", + id: "273cd167-f5a5-4da1-969e-d44213686491", traits: { namespace: "mail", name: "Mail Testserver", diff --git a/examples/mokapi/services_http.js b/examples/mokapi/services_http.js index e05708bdb..63b24e2e9 100644 --- a/examples/mokapi/services_http.js +++ b/examples/mokapi/services_http.js @@ -156,7 +156,11 @@ export let apps = [ { type: "query", name: "$format", - schema: {type: "string"} + schema: {type: "string"}, + required: false, + deprecated: false, + explode: false, + allowReserved: false } ], requestBody: { @@ -277,7 +281,10 @@ export let apps = [ type: "integer", format: "int64" }, - required: true + required: false, + deprecated: false, + explode: false, + allowReserved: false }, { name: "name", @@ -286,7 +293,10 @@ export let apps = [ schema: { type: "string" }, - required: false + required: false, + deprecated: false, + explode: false, + allowReserved: false }, { name: "status", @@ -295,7 +305,10 @@ export let apps = [ schema: { type: "string" }, - required: false + required: false, + deprecated: false, + explode: false, + allowReserved: false } ], responses: [ @@ -337,7 +350,10 @@ export let apps = [ type: "integer", format: "int64" }, - required: true + required: false, + deprecated: false, + explode: false, + allowReserved: false } ], responses: [ @@ -377,7 +393,9 @@ export let apps = [ type: "string", enum: ["available", "pending", "sold"] } - } + }, + deprecated: false, + allowReserved: false } ], responses: [ @@ -521,7 +539,7 @@ export let apps = [ operations: [ { method: "get", - summary: "Add a new pet to the store", + summary: "Get books from the store", operationId: "listBooks", responses: [ { @@ -573,7 +591,7 @@ export let apps = [ export let events = [ { - id: "4242", + id: "9e058601-562a-4063-a3b2-066ba624893a", traits: { namespace: "http", name: "Swagger Petstore", diff --git a/go.mod b/go.mod index 31f7c209f..1d304c69c 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,15 @@ module mokapi -go 1.25.1 +go 1.25.5 require ( github.com/Masterminds/sprig v2.22.0+incompatible - github.com/blevesearch/bleve/v2 v2.5.6 + github.com/blevesearch/bleve/v2 v2.5.7 github.com/blevesearch/bleve_index_api v1.2.11 github.com/bradleyfalzon/ghinstallation/v2 v2.17.0 github.com/brianvoe/gofakeit/v6 v6.28.0 github.com/dop251/goja v0.0.0-20250309171923-bcd7cc6bf64c - github.com/evanw/esbuild v0.27.1 + github.com/evanw/esbuild v0.27.2 github.com/fsnotify/fsnotify v1.9.0 github.com/go-co-op/gocron v1.37.0 github.com/go-git/go-git/v5 v5.16.4 @@ -49,7 +49,7 @@ require ( github.com/blevesearch/zapx/v13 v13.4.2 // indirect github.com/blevesearch/zapx/v14 v14.4.2 // indirect github.com/blevesearch/zapx/v15 v15.4.2 // indirect - github.com/blevesearch/zapx/v16 v16.2.7 // indirect + github.com/blevesearch/zapx/v16 v16.2.8 // indirect github.com/cloudflare/circl v1.6.1 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index 5783117b1..e9f71cbbc 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4= github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/blevesearch/bleve/v2 v2.5.6 h1:YdixQmOUuZHojQRe8Te7BY2cRirbzpbcpybAFs0m2DI= -github.com/blevesearch/bleve/v2 v2.5.6/go.mod h1:t5WoESS5TDteTdnjhhvpA1BpLYErOBX2IQViTMLK7wo= +github.com/blevesearch/bleve/v2 v2.5.7 h1:2d9YrL5zrX5EBBW++GOaEKjE+NPWeZGaX77IM26m1Z8= +github.com/blevesearch/bleve/v2 v2.5.7/go.mod h1:yj0NlS7ocGC4VOSAedqDDMktdh2935v2CSWOCDMHdSA= github.com/blevesearch/bleve_index_api v1.2.11 h1:bXQ54kVuwP8hdrXUSOnvTQfgK0KI1+f9A0ITJT8tX1s= github.com/blevesearch/bleve_index_api v1.2.11/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0= github.com/blevesearch/geo v0.2.4 h1:ECIGQhw+QALCZaDcogRTNSJYQXRtC8/m8IKiA706cqk= @@ -56,8 +56,8 @@ github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8= github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k= github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw= -github.com/blevesearch/zapx/v16 v16.2.7 h1:xcgFRa7f/tQXOwApVq7JWgPYSlzyUMmkuYa54tMDuR0= -github.com/blevesearch/zapx/v16 v16.2.7/go.mod h1:murSoCJPCk25MqURrcJaBQ1RekuqSCSfMjXH4rHyA14= +github.com/blevesearch/zapx/v16 v16.2.8 h1:SlnzF0YGtSlrsOE3oE7EgEX6BIepGpeqxs1IjMbHLQI= +github.com/blevesearch/zapx/v16 v16.2.8/go.mod h1:murSoCJPCk25MqURrcJaBQ1RekuqSCSfMjXH4rHyA14= github.com/bradleyfalzon/ghinstallation/v2 v2.17.0 h1:SmbUK/GxpAspRjSQbB6ARvH+ArzlNzTtHydNyXUQ6zg= github.com/bradleyfalzon/ghinstallation/v2 v2.17.0/go.mod h1:vuD/xvJT9Y+ZVZRv4HQ42cMyPFIYqpc7AbB4Gvt/DlY= github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= @@ -81,8 +81,8 @@ github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/evanw/esbuild v0.27.1 h1:SkYgb1wrwJkJYwBp5hjmQGm3riUHbCdfViyEvjAA/KA= -github.com/evanw/esbuild v0.27.1/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48= +github.com/evanw/esbuild v0.27.2 h1:3xBEws9y/JosfewXMM2qIyHAi+xRo8hVx475hVkJfNg= +github.com/evanw/esbuild v0.27.2/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= diff --git a/images/alpha.Dockerfile b/images/alpha.Dockerfile index bfc4e3993..73ffde179 100644 --- a/images/alpha.Dockerfile +++ b/images/alpha.Dockerfile @@ -9,9 +9,9 @@ WORKDIR webui COPY ./docs ./src/assets/docs RUN npm install -RUN npm run build +RUN npm run build-dashboard -FROM golang:1.25.1-alpine AS gobuild +FROM golang:1.25.5-alpine AS gobuild ARG VERSION=dev diff --git a/imap/idle_test.go b/imap/idle_test.go index eb9677c25..f527b2a0b 100644 --- a/imap/idle_test.go +++ b/imap/idle_test.go @@ -178,6 +178,8 @@ func TestIdle(t *testing.T) { require.NoError(t, err) require.Equal(t, "+ idling", res) + time.Sleep(500 * time.Millisecond) + res, err = c.ReadLine() require.NoError(t, err) require.Equal(t, "* 10 EXISTS", res) diff --git a/js/mokapi/cron.go b/js/mokapi/cron.go index 7a1b8b9cd..c21ef332b 100644 --- a/js/mokapi/cron.go +++ b/js/mokapi/cron.go @@ -11,7 +11,11 @@ import ( ) func (m *Module) Cron(expr string, do goja.Value, args goja.Value) (int, error) { - options, err := getJobOptions(m.vm, args) + options, err := getJobOptions(m.vm, args, common.JobOptions{ + Tags: map[string]string{}, + Times: -1, + SkipImmediateFirstRun: true, + }) if err != nil { panic(m.vm.ToValue(err.Error())) } @@ -33,8 +37,8 @@ func (m *Module) Cron(expr string, do goja.Value, args goja.Value) (int, error) return m.host.Cron(expr, f, options) } -func getJobOptions(vm *goja.Runtime, opt goja.Value) (common.JobOptions, error) { - options := common.NewJobOptions() +func getJobOptions(vm *goja.Runtime, opt goja.Value, defaultOptions common.JobOptions) (common.JobOptions, error) { + options := defaultOptions if opt != nil && !goja.IsUndefined(opt) && !goja.IsNull(opt) { if opt.ExportType().Kind() != reflect.Map { diff --git a/js/mokapi/every.go b/js/mokapi/every.go index 5bd65f645..dcb299f37 100644 --- a/js/mokapi/every.go +++ b/js/mokapi/every.go @@ -1,13 +1,18 @@ package mokapi import ( + "mokapi/engine/common" "mokapi/js/eventloop" "github.com/dop251/goja" ) func (m *Module) Every(every string, do goja.Value, args goja.Value) (int, error) { - options, err := getJobOptions(m.vm, args) + options, err := getJobOptions(m.vm, args, common.JobOptions{ + Tags: map[string]string{}, + Times: -1, + SkipImmediateFirstRun: false, + }) if err != nil { panic(m.vm.ToValue(err.Error())) } diff --git a/js/script.go b/js/script.go index b6828c059..a26504457 100644 --- a/js/script.go +++ b/js/script.go @@ -24,6 +24,7 @@ import ( "github.com/dop251/goja" "github.com/pkg/errors" + log "github.com/sirupsen/logrus" ) var ( @@ -69,6 +70,10 @@ func (s *Script) Run() error { _, err := s.RunDefault() if err != nil { if errors.Is(err, NoDefaultFunction) { + log.Debugf( + "skipping JavaScript file %s: no default export (not an executable script)", + s.file.Info.Url, + ) return nil } return err diff --git a/kafka/request_test.go b/kafka/request_test.go index c526433ac..3dcc7b42a 100644 --- a/kafka/request_test.go +++ b/kafka/request_test.go @@ -203,7 +203,7 @@ func Test_Offset(t *testing.T) { require.Equal(t, b, buf.Bytes()) } -func Test_ApiVersion(t *testing.T) { +func Test_ApiVersion_Version_Newer(t *testing.T) { req := &kafka.Request{ Host: "", Header: &kafka.Header{ @@ -226,3 +226,29 @@ func Test_ApiVersion(t *testing.T) { err = result.Read(r) require.NoError(t, err) } + +func Test_ApiVersion_Tagged(t *testing.T) { + req := &kafka.Request{ + Host: "", + Header: &kafka.Header{ + Size: 0, + ApiKey: kafka.ApiVersions, + ApiVersion: 3, + CorrelationId: 0, + }, + Message: &apiVersion.Request{ + TagFields: map[int64]string{3: "abc"}, + }, + } + var buf bytes.Buffer + w := bufio.NewWriter(&buf) + err := req.Write(w) + require.NoError(t, err) + err = w.Flush() + require.NoError(t, err) + + r := bufio.NewReader(&buf) + result := &kafka.Request{} + err = result.Read(r) + require.NoError(t, err) +} diff --git a/ldap/compare.go b/ldap/compare.go index 697108aae..7053affa5 100644 --- a/ldap/compare.go +++ b/ldap/compare.go @@ -1,6 +1,10 @@ package ldap -import ber "gopkg.in/go-asn1-ber/asn1-ber.v1" +import ( + "fmt" + + ber "gopkg.in/go-asn1-ber/asn1-ber.v1" +) type CompareRequest struct { Dn string `json:"dn"` @@ -14,19 +18,55 @@ type CompareResponse struct { } func decodeCompareRequest(body *ber.Packet) (*CompareRequest, error) { - r := &CompareRequest{ - Dn: body.Children[0].Value.(string), - Attribute: body.Children[1].Value.(string), - Value: body.Children[2].Value.(string), + if len(body.Children) != 2 { + return nil, fmt.Errorf("invalid compare request: expected 2 children, got %d", len(body.Children)) + } + + dnPacket := body.Children[0] + kvPacket := body.Children[1] + + if len(kvPacket.Children) != 2 { + return nil, fmt.Errorf("invalid attribute value assertion: expected 2 children") + } + + r := &CompareRequest{} + + // DN + if dn, ok := dnPacket.Value.(string); ok { + r.Dn = dn + } else { + return nil, fmt.Errorf("dn is not a string") } + + // Attribute + if attr, ok := kvPacket.Children[0].Value.(string); ok { + r.Attribute = attr + } else { + return nil, fmt.Errorf("attribute is not a string") + } + + // Value (string or []byte depending on encoding) + switch v := kvPacket.Children[1].Value.(type) { + case string: + r.Value = v + case []byte: + r.Value = string(v) + default: + return nil, fmt.Errorf("assertion value is not string or []byte") + } + return r, nil } func (r *CompareRequest) encode(envelope *ber.Packet) { body := ber.Encode(ber.ClassApplication, ber.TypeConstructed, compareRequest, nil, "compare request") body.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, r.Dn, "DN")) - body.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, r.Attribute, "attribute")) - body.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, r.Value, "value")) + + ava := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "AttributeValueAssertion") + ava.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, r.Attribute, "attribute")) + ava.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, r.Value, "value")) + body.AppendChild(ava) + envelope.AppendChild(body) } diff --git a/ldap/filter.go b/ldap/filter.go index bfd53484a..17ae91a07 100644 --- a/ldap/filter.go +++ b/ldap/filter.go @@ -3,8 +3,9 @@ package ldap import ( "bytes" "fmt" - ber "gopkg.in/go-asn1-ber/asn1-ber.v1" "strings" + + ber "gopkg.in/go-asn1-ber/asn1-ber.v1" ) func compileFilter(filter string) (*ber.Packet, int, error) { @@ -161,6 +162,53 @@ func decompileFilter(p *ber.Packet) (string, error) { } } return fmt.Sprintf("(%v=%v)", p.Children[0].Value.(string), sb.String()), nil + case FilterExtensibleMatch: + // child tags: + // [1] matchingRule (OID) + // [2] type (attribute) + // [3] matchValue + // [4] dnAttributes (boolean) + var attr, rule, val string + var dnAttr bool + + for _, c := range p.Children { + switch c.Tag { + case 1: // matchingRule OID + rule = getString(c) + case 2: // attribute description + attr = getString(c) + case 3: // matchValue + val = getString(c) + case 4: // dnAttributes + dnAttr = c.Value.(bool) + } + } + + // Build the filter string in LDAP syntax + // Examples: + // (attr:rule:=value) + // (:rule:=value) + // (attr:=value) + // (attr:dn:=value) for DN attributes + var sb strings.Builder + sb.WriteString("(") + if attr != "" { + sb.WriteString(attr) + } + sb.WriteString(":") + + if rule != "" { + sb.WriteString(rule) + } + if dnAttr { + sb.WriteString(":dn") + } + + sb.WriteString(":=") + sb.WriteString(val) + sb.WriteString(")") + + return sb.String(), nil default: return "", fmt.Errorf("unsupported filter %v requested", p.Tag) } @@ -184,3 +232,14 @@ func parseUnary(children []*ber.Packet) (string, error) { } return decompileFilter(children[0]) } + +func getString(p *ber.Packet) string { + switch v := p.Value.(type) { + case string: + return v + case []byte: + return string(v) + default: + return p.Data.String() + } +} diff --git a/ldap/modifyDn.go b/ldap/modifyDn.go index 408c2769b..f6b14b69b 100644 --- a/ldap/modifyDn.go +++ b/ldap/modifyDn.go @@ -1,6 +1,10 @@ package ldap -import ber "gopkg.in/go-asn1-ber/asn1-ber.v1" +import ( + "fmt" + + ber "gopkg.in/go-asn1-ber/asn1-ber.v1" +) type ModifyDNRequest struct { Dn string @@ -16,14 +20,51 @@ type ModifyDNResponse struct { } func decodeModifyDNRequest(body *ber.Packet) (*ModifyDNRequest, error) { - r := &ModifyDNRequest{ - Dn: body.Children[0].Value.(string), - NewRdn: body.Children[1].Value.(string), - DeleteOldDn: body.Children[2].Value.(bool), + r := &ModifyDNRequest{} + + // 1. Entry (DN) + entry, ok := body.Children[0].Value.(string) + if !ok { + return nil, fmt.Errorf("modifyDN request: entry is not a string") + } + r.Dn = entry + + // 2. New RDN + newRDN, ok := body.Children[1].Value.(string) + if !ok { + return nil, fmt.Errorf("modifyDN request: newRDN is not a string") } - if len(body.Children) >= 4 { - r.NewSuperiorDn = body.Children[3].Value.(string) + r.NewRdn = newRDN + + // 3. deleteOldRDN + deleteOld, ok := body.Children[2].Value.(bool) + if !ok { + // Some BER implementations decode BOOLEAN as uint8 + if b, ok2 := body.Children[2].Value.(uint8); ok2 { + r.DeleteOldDn = b != 0 + } else { + return nil, fmt.Errorf("modifyDN request: deleteOldRDN not a bool") + } + } else { + r.DeleteOldDn = deleteOld } + + // 4. Optional newSuperior + if len(body.Children) == 4 { + // newSuperior is tagged with context-specific [0] + ns := body.Children[3] + if ns.Tag != 0 || ns.ClassType != ber.ClassContext { + return nil, fmt.Errorf("modifyDN request: invalid newSuperior tag") + } + + newSup, ok := ns.Value.(string) + if !ok { + newSup = string(ns.Data.Bytes()) + } + + r.NewSuperiorDn = newSup + } + return r, nil } diff --git a/ldap/protocol.go b/ldap/protocol.go index dc5883d80..28a9b8de6 100644 --- a/ldap/protocol.go +++ b/ldap/protocol.go @@ -23,15 +23,16 @@ const ( compareResponse = 15 abandonRequest = 16 - FilterAnd = 0 - FilterOr = 1 - FilterNot = 2 - FilterEqualityMatch = 3 - FilterSubstrings = 4 - FilterGreaterOrEqual = 5 - FilterLessOrEqual = 6 - FilterPresent = 7 - FilterApproxMatch = 8 + FilterAnd = 0 + FilterOr = 1 + FilterNot = 2 + FilterEqualityMatch = 3 + FilterSubstrings = 4 + FilterGreaterOrEqual = 5 + FilterLessOrEqual = 6 + FilterPresent = 7 + FilterApproxMatch = 8 + FilterExtensibleMatch = 9 FilterSubstringsStartWith uint8 = 0 FilterSubstringsAny uint8 = 1 diff --git a/ldap/server.go b/ldap/server.go index 1672609eb..b4e79541a 100644 --- a/ldap/server.go +++ b/ldap/server.go @@ -3,9 +3,6 @@ package ldap import ( "context" "fmt" - "github.com/pkg/errors" - log "github.com/sirupsen/logrus" - ber "gopkg.in/go-asn1-ber/asn1-ber.v1" "io" "net" "reflect" @@ -13,6 +10,10 @@ import ( "sync" "sync/atomic" "syscall" + + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + ber "gopkg.in/go-asn1-ber/asn1-ber.v1" ) var ErrServerClosed = errors.New("ldap: Server closed") @@ -25,6 +26,7 @@ func (a *atomicBool) setTrue() { atomic.StoreInt32((*int32)(a), 1) } type Handler interface { ServeLDAP(ResponseWriter, *Request) + Unbind(ctx context.Context) } type HandlerFunc func(rw ResponseWriter, req *Request) @@ -33,6 +35,8 @@ func (f HandlerFunc) ServeLDAP(rw ResponseWriter, req *Request) { f(rw, req) } +func (f HandlerFunc) Unbind(ctx context.Context) {} + type Message interface { } @@ -171,6 +175,7 @@ func (s *Server) serve(conn net.Conn, ctx context.Context) { msg, err = readBindRequest(body) case unbindRequest: log.Debugf("ldap: received unbind request") + s.Handler.Unbind(ctx) return case searchRequest: msg, err = decodeSearchRequest(body, controls) diff --git a/providers/directory/config.go b/providers/directory/config.go index a095f556d..b9ddf8747 100644 --- a/providers/directory/config.go +++ b/providers/directory/config.go @@ -8,6 +8,8 @@ import ( "net/url" "path/filepath" "strings" + + log "github.com/sirupsen/logrus" ) type Config struct { @@ -34,7 +36,7 @@ type Info struct { } func (c *Config) Parse(config *dynamic.Config, reader dynamic.Reader) error { - c.Entries = &sortedmap.LinkedHashMap[string, Entry]{} + c.Entries = &sortedmap.LinkedHashMap[string, Entry]{KeyNormalizer: normalizeDN} // copy from old format for k, v := range c.oldEntries { c.Entries.Set(k, v) @@ -168,6 +170,8 @@ func (c *Config) Parse(config *dynamic.Config, reader dynamic.Reader) error { } } + c.rebuildMemberOf() + return nil } @@ -175,6 +179,43 @@ func (c *Config) getSizeLimit() int64 { return c.SizeLimit } +func (c *Config) rebuildMemberOf() { + // reset entries + + groups := map[string][]string{} + for it := c.Entries.Iter(); it.Next(); { + groupName := it.Value().Dn + for k, v := range it.Value().Attributes { + if k == "member" { + if _, ok := groups[k]; !ok { + groups[k] = []string{} + } + for _, member := range v { + groups[groupName] = append(groups[groupName], member) + } + } + if k == "memberOf" { + delete(it.Value().Attributes, "memberOf") + } + } + } + + for group, members := range groups { + for _, member := range members { + e, ok := c.Entries.Get(member) + if !ok { + log.Warnf("LDAP: member %s for group %s not found", member, group) + continue + } + if e.Attributes == nil { + e.Attributes = map[string][]string{} + } + e.Attributes["memberOf"] = append(e.Attributes["memberOf"], group) + c.Entries.Set(member, e) + } + } +} + func (e *Entry) Parse(config *dynamic.Config, reader dynamic.Reader) error { if aList, ok := e.Attributes["thumbnailphoto"]; ok { for i, a := range aList { @@ -208,13 +249,13 @@ func (e *Entry) Parse(config *dynamic.Config, reader dynamic.Reader) error { return nil } -func formatDN(dn string) string { +func normalizeDN(dn string) string { result := "" for _, rdn := range strings.Split(dn, ",") { if len(result) > 0 { result += "," } - result += strings.TrimSpace(rdn) + result += strings.TrimSpace(strings.ToLower(rdn)) } return result } diff --git a/providers/directory/config_ldif_test.go b/providers/directory/config_ldif_test.go index 350be8661..d11b5dbe0 100644 --- a/providers/directory/config_ldif_test.go +++ b/providers/directory/config_ldif_test.go @@ -2,11 +2,12 @@ package directory import ( "encoding/json" - "github.com/stretchr/testify/require" "mokapi/config/dynamic" "mokapi/config/dynamic/dynamictest" "mokapi/try" "testing" + + "github.com/stretchr/testify/require" ) func TestLdif_Parse(t *testing.T) { @@ -23,7 +24,7 @@ func TestLdif_Parse(t *testing.T) { require.NoError(t, err) require.Len(t, ld.Records, 1) require.Equal(t, &AddRecord{ - Dn: "dc=mokapi,dc=io", + Dn: "dc=mokapi, dc=io", }, ld.Records[0]) }, }, @@ -34,10 +35,10 @@ func TestLdif_Parse(t *testing.T) { require.NoError(t, err) require.Len(t, ld.Records, 2) require.Equal(t, &AddRecord{ - Dn: "dc=mokapi,dc=io", + Dn: "dc=mokapi, dc=io", }, ld.Records[0]) require.Equal(t, &AddRecord{ - Dn: "ou=Sales,dc=mokapi,dc=io", + Dn: "ou=Sales, dc=mokapi, dc=io", }, ld.Records[1]) }, }, @@ -48,10 +49,10 @@ func TestLdif_Parse(t *testing.T) { require.NoError(t, err) require.Len(t, ld.Records, 2) require.Equal(t, &AddRecord{ - Dn: "dc=mokapi,dc=io", + Dn: "dc=mokapi, dc=io", }, ld.Records[0]) require.Equal(t, &AddRecord{ - Dn: "ou=Sales,dc=mokapi,dc=io", + Dn: "ou=Sales, dc=mokapi, dc=io", }, ld.Records[1]) }, }, @@ -69,7 +70,7 @@ func TestLdif_Parse(t *testing.T) { require.NoError(t, err) require.Len(t, ld.Records, 1) require.Equal(t, &AddRecord{ - Dn: "dc=mokapi,dc=io", + Dn: "dc=mokapi, dc=io", Attributes: map[string][]string{"description": {"foo bar"}}, }, ld.Records[0]) }, @@ -81,7 +82,7 @@ func TestLdif_Parse(t *testing.T) { require.NoError(t, err) require.Len(t, ld.Records, 1) require.Equal(t, &AddRecord{ - Dn: "dc=mokapi,dc=io", + Dn: "dc=mokapi, dc=io", Attributes: map[string][]string{"attribute": {"foo"}}, }, ld.Records[0]) }, @@ -93,7 +94,7 @@ func TestLdif_Parse(t *testing.T) { require.NoError(t, err) require.Len(t, ld.Records, 1) require.Equal(t, &AddRecord{ - Dn: "dc=mokapi,dc=io", + Dn: "dc=mokapi, dc=io", }, ld.Records[0]) }, }, @@ -104,7 +105,7 @@ func TestLdif_Parse(t *testing.T) { require.NoError(t, err) require.Len(t, ld.Records, 1) require.Equal(t, &AddRecord{ - Dn: "dc=mokapi,dc=io", + Dn: "dc=mokapi, dc=io", Attributes: map[string][]string{"foo": {"bar"}}, }, ld.Records[0]) }, @@ -116,7 +117,7 @@ func TestLdif_Parse(t *testing.T) { require.NoError(t, err) require.Len(t, ld.Records, 1) require.Equal(t, &AddRecord{ - Dn: "dc=mokapi,dc=io", + Dn: "dc=mokapi, dc=io", }, ld.Records[0]) }, }, @@ -127,7 +128,7 @@ func TestLdif_Parse(t *testing.T) { require.NoError(t, err) require.Len(t, ld.Records, 1) require.Equal(t, &AddRecord{ - Dn: "dc=mokapi,dc=io", + Dn: "dc=mokapi, dc=io", Attributes: map[string][]string{"foo": {""}}, }, ld.Records[0]) }, @@ -139,7 +140,7 @@ func TestLdif_Parse(t *testing.T) { require.NoError(t, err) require.Len(t, ld.Records, 1) require.Equal(t, &AddRecord{ - Dn: "dc=mokapi,dc=io", + Dn: "dc=mokapi, dc=io", }, ld.Records[0]) }, }, @@ -150,7 +151,7 @@ func TestLdif_Parse(t *testing.T) { require.NoError(t, err) require.Len(t, ld.Records, 1) require.Equal(t, &DeleteRecord{ - Dn: "dc=mokapi,dc=io", + Dn: "dc=mokapi, dc=io", }, ld.Records[0]) }, }, @@ -161,7 +162,7 @@ func TestLdif_Parse(t *testing.T) { require.NoError(t, err) require.Len(t, ld.Records, 1) require.Equal(t, &ModifyRecord{ - Dn: "dc=mokapi,dc=io", + Dn: "dc=mokapi, dc=io", }, ld.Records[0]) }, }, @@ -172,7 +173,7 @@ func TestLdif_Parse(t *testing.T) { require.NoError(t, err) require.Len(t, ld.Records, 1) require.Equal(t, &ModifyRecord{ - Dn: "dc=mokapi,dc=io", + Dn: "dc=mokapi, dc=io", Actions: []*ModifyAction{ { Type: "add", @@ -190,7 +191,7 @@ func TestLdif_Parse(t *testing.T) { require.NoError(t, err) require.Len(t, ld.Records, 1) require.Equal(t, &ModifyRecord{ - Dn: "dc=mokapi,dc=io", + Dn: "dc=mokapi, dc=io", Actions: []*ModifyAction{ { Type: "replace", @@ -208,7 +209,7 @@ func TestLdif_Parse(t *testing.T) { require.NoError(t, err) require.Len(t, ld.Records, 1) require.Equal(t, &ModifyRecord{ - Dn: "dc=mokapi,dc=io", + Dn: "dc=mokapi, dc=io", Actions: []*ModifyAction{ { Type: "delete", @@ -232,7 +233,7 @@ func TestLdif_Parse(t *testing.T) { require.NoError(t, err) require.Len(t, ld.Records, 1) require.Equal(t, &AddRecord{ - Dn: "dc=mokapi,dc=io", + Dn: "dc=mokapi, dc=io", Attributes: map[string][]string{ "photo": {"1234"}, }, @@ -246,7 +247,7 @@ func TestLdif_Parse(t *testing.T) { require.NoError(t, err) require.Len(t, ld.Records, 1) require.Equal(t, &AddRecord{ - Dn: "dc=mokapi,dc=io", + Dn: "dc=mokapi, dc=io", Attributes: map[string][]string{ "description": {"foobar"}, }, @@ -333,7 +334,7 @@ func TestConfig_LDIF(t *testing.T) { test: func(t *testing.T, c *Config, err error) { require.NoError(t, err) require.Equal(t, 3, c.Entries.Len()) - require.Len(t, c.Entries.Lookup("dc=mokapi,dc=io").Attributes, 0) + require.Len(t, c.Entries.Lookup("dc=mokapi, dc=io").Attributes, 0) }, }, { diff --git a/providers/directory/directory.go b/providers/directory/directory.go index 63a60f1de..1bedc1c9b 100644 --- a/providers/directory/directory.go +++ b/providers/directory/directory.go @@ -3,13 +3,14 @@ package directory import ( "context" "errors" - log "github.com/sirupsen/logrus" engine "mokapi/engine/common" "mokapi/ldap" "mokapi/runtime/events" "mokapi/runtime/monitor" "slices" "time" + + log "github.com/sirupsen/logrus" ) type Directory struct { @@ -53,46 +54,60 @@ func (d *Directory) serveBind(rw ldap.ResponseWriter, r *ldap.Request) { return } + var res *ldap.BindResponse switch msg.Auth { case ldap.Simple: log.Debugf("received bind request with messageId %v, version %v. auth: %v", r.MessageId, msg.Version, msg.Name) - if m, ok := monitor.LdapFromContext(r.Context); ok { - m.RequestCounter.WithLabel(d.config.Info.Name, "bind").Add(1) - } if msg.Name != "" { e := d.getEntry(msg.Name) if e == nil { - rw.Write(&ldap.BindResponse{ + res = &ldap.BindResponse{ Result: ldap.InvalidCredentials, - }) - return - } - pw, ok := e.Attributes["userPassword"] - if !ok { - rw.Write(&ldap.BindResponse{ - Result: ldap.Success, - }) - } else if pw[0] == msg.Password { - rw.Write(&ldap.BindResponse{ - Result: ldap.Success, - }) + } } else { - rw.Write(&ldap.BindResponse{ - Result: ldap.InvalidCredentials, - }) + pw, ok := e.Attributes["userPassword"] + if !ok { + res = &ldap.BindResponse{ + Result: ldap.Success, + } + } else if pw[0] == msg.Password { + res = &ldap.BindResponse{ + Result: ldap.Success, + } + } else { + res = &ldap.BindResponse{ + Result: ldap.InvalidCredentials, + } + } } } else { - rw.Write(&ldap.BindResponse{ + res = &ldap.BindResponse{ Result: ldap.Success, - }) + } } default: - rw.Write(&ldap.BindResponse{ + res = &ldap.BindResponse{ Result: ldap.AuthMethodNotSupported, Message: "server supports only simple auth method", - }) + } + } + + m, doMonitor := monitor.LdapFromContext(r.Context) + if doMonitor { + l := NewBindLogEvent(msg, res, d.eh, events.NewTraits().WithName(d.config.Info.Name)) + defer func() { + i := r.Context.Value("time") + if i != nil { + t := i.(time.Time) + l.Duration = time.Now().Sub(t).Milliseconds() + } + }() + m.RequestCounter.WithLabel(d.config.Info.Name, "modify").Add(1) + m.LastRequest.WithLabel(d.config.Info.Name).Set(float64(time.Now().Unix())) } + + rw.Write(res) } func (d *Directory) skip(e *Entry, baseDN string) bool { @@ -146,6 +161,7 @@ func (d *Directory) serveModify(rw ldap.ResponseWriter, r *ldap.ModifyRequest, c res = &ldap.ModifyResponse{ResultCode: ee.Code, MatchedDn: r.Dn, Message: err.Error()} } else { res = &ldap.ModifyResponse{ResultCode: ldap.Success, MatchedDn: r.Dn} + go d.config.rebuildMemberOf() } m, doMonitor := monitor.LdapFromContext(ctx) @@ -165,6 +181,20 @@ func (d *Directory) serveModify(rw ldap.ResponseWriter, r *ldap.ModifyRequest, c rw.Write(res) } +func (d *Directory) Unbind(ctx context.Context) { + m, doMonitor := monitor.LdapFromContext(ctx) + if doMonitor { + l := NewUnbindEvent(d.eh, events.NewTraits().WithName(d.config.Info.Name)) + i := ctx.Value("time") + if i != nil { + t := i.(time.Time) + l.Duration = time.Now().Sub(t).Milliseconds() + } + m.RequestCounter.WithLabel(d.config.Info.Name, "unbind").Add(1) + m.LastRequest.WithLabel(d.config.Info.Name).Set(float64(time.Now().Unix())) + } +} + func (d *Directory) serveAdd(rw ldap.ResponseWriter, r *ldap.AddRequest, ctx context.Context) { add := &AddRecord{ Dn: r.Dn, @@ -182,6 +212,7 @@ func (d *Directory) serveAdd(rw ldap.ResponseWriter, r *ldap.AddRequest, ctx con res = &ldap.AddResponse{ResultCode: ee.Code, Message: err.Error()} } else { res = &ldap.AddResponse{ResultCode: ldap.Success, MatchedDn: r.Dn} + go d.config.rebuildMemberOf() } m, doMonitor := monitor.LdapFromContext(ctx) @@ -213,6 +244,7 @@ func (d *Directory) serveDelete(rw ldap.ResponseWriter, r *ldap.DeleteRequest, c res = &ldap.DeleteResponse{ResultCode: ee.Code, MatchedDn: del.Dn, Message: err.Error()} } else { res = &ldap.DeleteResponse{ResultCode: ldap.Success, MatchedDn: del.Dn} + go d.config.rebuildMemberOf() } m, doMonitor := monitor.LdapFromContext(ctx) @@ -247,6 +279,7 @@ func (d *Directory) serveModifyDn(rw ldap.ResponseWriter, r *ldap.ModifyDNReques res = &ldap.ModifyDNResponse{ResultCode: ee.Code, MatchedDn: r.Dn, Message: err.Error()} } else { res = &ldap.ModifyDNResponse{ResultCode: ldap.Success, MatchedDn: r.Dn} + go d.config.rebuildMemberOf() } m, doMonitor := monitor.LdapFromContext(ctx) @@ -268,16 +301,17 @@ func (d *Directory) serveModifyDn(rw ldap.ResponseWriter, r *ldap.ModifyDNReques func (d *Directory) serveCompare(rw ldap.ResponseWriter, r *ldap.CompareRequest, ctx context.Context) { e := d.getEntry(r.Dn) + var res *ldap.CompareResponse if e != nil { if a, ok := e.Attributes[r.Attribute]; ok { if slices.Contains(a, r.Value) { - rw.Write(&ldap.CompareResponse{ResultCode: ldap.CompareTrue}) - return + res = &ldap.CompareResponse{ResultCode: ldap.CompareTrue} } } } - - res := &ldap.CompareResponse{ResultCode: ldap.CompareFalse} + if res == nil { + res = &ldap.CompareResponse{ResultCode: ldap.CompareFalse} + } m, doMonitor := monitor.LdapFromContext(ctx) if doMonitor { diff --git a/providers/directory/directory_test.go b/providers/directory/directory_test.go index 40abcb2ef..f58dbc0b2 100644 --- a/providers/directory/directory_test.go +++ b/providers/directory/directory_test.go @@ -2,7 +2,6 @@ package directory_test import ( "context" - "github.com/stretchr/testify/require" "mokapi/engine/enginetest" "mokapi/ldap" "mokapi/ldap/ldaptest" @@ -10,6 +9,8 @@ import ( "mokapi/runtime/events/eventstest" "mokapi/sortedmap" "testing" + + "github.com/stretchr/testify/require" ) var testConfig = &directory.Config{ @@ -67,7 +68,7 @@ func TestDirectory_ServeBind(t *testing.T) { Version: 3, Auth: 3, // sasl } - h.ServeLDAP(rr, &ldap.Request{Message: r}) + h.ServeLDAP(rr, ldaptest.NewRequest(0, r)) res := rr.Message.(*ldap.BindResponse) require.Equal(t, ldap.AuthMethodNotSupported, res.Result) require.Equal(t, "", res.MatchedDN) @@ -83,7 +84,7 @@ func TestDirectory_ServeBind(t *testing.T) { Version: 2, Auth: ldap.Simple, } - h.ServeLDAP(rr, &ldap.Request{Message: r}) + h.ServeLDAP(rr, ldaptest.NewRequest(0, r)) res := rr.Message.(*ldap.BindResponse) require.Equal(t, ldap.ProtocolError, res.Result) require.Equal(t, "", res.MatchedDN) diff --git a/providers/directory/entry.go b/providers/directory/entry.go index 2d9b51da6..ad1604f95 100644 --- a/providers/directory/entry.go +++ b/providers/directory/entry.go @@ -3,6 +3,7 @@ package directory import ( "fmt" "mokapi/ldap" + "strconv" ) type EntryError struct { @@ -83,3 +84,12 @@ func NewEntryError(code uint8, format string, args ...interface{}) *EntryError { Code: code, } } + +func (e *Entry) getInt(attr string) int { + vals, ok := e.Attributes[attr] + if !ok || len(vals) == 0 { + return 0 + } + i, _ := strconv.Atoi(vals[0]) + return i +} diff --git a/providers/directory/ldif.go b/providers/directory/ldif.go index bd99cc5c1..ee34928bb 100644 --- a/providers/directory/ldif.go +++ b/providers/directory/ldif.go @@ -83,7 +83,7 @@ func (l *Ldif) Parse(config *dynamic.Config, reader dynamic.Reader) error { switch kv[0] { case "dn": - dn = formatDN(val) + dn = val rec = &AddRecord{Dn: dn} case "changetype": switch val { diff --git a/providers/directory/log.go b/providers/directory/log.go index c4d623197..fb8476699 100644 --- a/providers/directory/log.go +++ b/providers/directory/log.go @@ -62,6 +62,31 @@ func fromRequest(r *ldap.SearchRequest) *SearchRequest { } } +type BindLog struct { + Request *BindRequest `json:"request"` + Response *Response `json:"response"` + Duration int64 `json:"duration"` + Actions []*common.Action `json:"actions"` +} + +type UnbindLog struct { + Request *UnbindRequest `json:"request"` + Duration int64 `json:"duration"` + Actions []*common.Action `json:"actions"` +} + +type UnbindRequest struct { + Operation string `json:"operation"` +} + +type BindRequest struct { + Operation string `json:"operation"` + Version int64 `json:"version"` + Name string `json:"name"` + Password string `json:"password"` + Auth string `json:"auth"` +} + type AddLog struct { Request *AddRequest `json:"request"` Response *Response `json:"response"` @@ -82,10 +107,36 @@ type Attribute struct { type Response struct { Status string `json:"status"` - MatchedDn string `json:"matchedDn"` + MatchedDn string `json:"matchedDn,omitempty"` Message string `json:"message"` } +func NewBindLogEvent(req *ldap.BindRequest, res *ldap.BindResponse, eh events.Handler, traits events.Traits) *BindLog { + l := &BindLog{ + Request: &BindRequest{ + Operation: "Bind", + Version: req.Version, + Name: req.Name, + Password: req.Password, + Auth: "Simple", + }, + Response: &Response{ + Status: ldap.StatusText[res.Result], + Message: res.Message, + }, + Duration: 0, + Actions: nil, + } + _ = eh.Push(l, traits.WithNamespace("ldap")) + return l +} + +func NewUnbindEvent(eh events.Handler, traits events.Traits) *UnbindLog { + l := &UnbindLog{Request: &UnbindRequest{Operation: "Unbind"}} + _ = eh.Push(l, traits.WithNamespace("ldap")) + return l +} + func NewAddLogEvent(req *ldap.AddRequest, res *ldap.AddResponse, eh events.Handler, traits events.Traits) *AddLog { var attr []Attribute for _, v := range req.Attributes { @@ -265,6 +316,14 @@ func NewCompareLogEvent(req *ldap.CompareRequest, res *ldap.CompareResponse, eh return l } +func (l *BindLog) Title() string { + return fmt.Sprintf("%s %s %s", l.Request.Auth, l.Request.Name, l.Request.Password) +} + +func (l *UnbindLog) Title() string { + return "" +} + func (l *SearchLog) Title() string { return l.Request.Filter } diff --git a/providers/directory/search.go b/providers/directory/search.go index 7dfa12d60..96023b2f6 100644 --- a/providers/directory/search.go +++ b/providers/directory/search.go @@ -170,13 +170,14 @@ func (p *parser) parse(filter string) (predicate, int, error) { var attr *bytes.Buffer var v *bytes.Buffer var op int + var rule string for pos := 0; pos < len(filter); pos++ { c := filter[pos] switch { case c == '(': v = bytes.NewBuffer(nil) case c == ')': - p, err := p.predicate(op, attr.String(), v.String()) + p, err := p.predicate(op, attr.String(), v.String(), rule) return p, pos + 1, err case c == '=' && op == 0: op = ldap.FilterEqualityMatch @@ -206,7 +207,25 @@ func (p *parser) parse(filter string) (predicate, int, error) { case c == '|': fs, n, err := p.parseSet(filter[pos+1:]) return or(fs), pos + n + 2, err + case c == ':': + // We have an extensible match. Now look ahead. + // Find the next ':=' which separates rule from value. + ruleStart := pos + 1 + ruleEnd := strings.Index(filter[ruleStart:], ":=") + if ruleEnd < 0 { + return nil, 0, fmt.Errorf("invalid extensible filter syntax") + } + ruleEnd += ruleStart + + // Extract matching rule OID + rule = filter[ruleStart:ruleEnd] + // Move parser position to after "rule:=" + pos = ruleEnd + 1 // because pos++ will run + + op = ldap.FilterExtensibleMatch + attr = v // attribute name + v = bytes.NewBuffer(nil) default: v.WriteByte(c) } @@ -230,7 +249,11 @@ func (p *parser) parseSet(filter string) ([]predicate, int, error) { return fs, pos, nil } -func (p *parser) predicate(op int, name, value string) (predicate, error) { +func (p *parser) predicate(op int, name, value, rule string) (predicate, error) { + if strings.ToLower(name) == "memberof" { + value = normalizeDN(value) + } + switch op { case ldap.FilterEqualityMatch: if strings.Contains(value, "*") { @@ -252,6 +275,8 @@ func (p *parser) predicate(op int, name, value string) (predicate, error) { threshold := n / 5 return distance <= threshold }), nil + case ldap.FilterExtensibleMatch: + return p.predicateExtensible(name, rule, value), nil default: return nil, fmt.Errorf("unsupported filter operation for attribute %v: %v", name, op) } @@ -312,7 +337,7 @@ func (p *parser) substring(name, value string) predicate { func (p *parser) equal(name, value string) (predicate, error) { f := func(s string) bool { - return value == s + return strings.ToLower(value) == strings.ToLower(s) } if p.s != nil { @@ -474,6 +499,25 @@ func (p *parser) check(name string, f func(string) bool) predicate { } } +func (p *parser) predicateExtensible(name, rule, value string) predicate { + switch rule { + case "1.2.840.113556.1.4.803": // bitwise AND + mask, _ := strconv.Atoi(value) + return func(e Entry) bool { + v := e.getInt(name) + return v&mask == mask + } + case "1.2.840.113556.1.4.804": // bitwise OR + mask, _ := strconv.Atoi(value) + return func(e Entry) bool { + v := e.getInt(name) + return v&mask != 0 + } + } + // fallback: unsupported rule + return func(e Entry) bool { return false } +} + func getPageInfo(controls []ldap.Control, ctx context.Context) (int64, int64) { for _, control := range controls { switch ctrl := control.(type) { @@ -562,7 +606,10 @@ func sidToBytes(sid string) ([]byte, error) { // Revision rev, revErr := strconv.ParseUint(parts[0], 10, 32) if revErr != nil { - return nil, fmt.Errorf("invalid uint value %v at position: %v", parts[0], 0) + return nil, fmt.Errorf("invalid SID revision value value '%v' at position: %v", parts[0], 0) + } + if rev > 255 { + return nil, fmt.Errorf("SID revision value '%v' out of byte range (0-255) at position: %v", parts[1], 0) } result = append(result, byte(rev)) diff --git a/providers/directory/search_test.go b/providers/directory/search_test.go index 846f4a5ad..614f16483 100644 --- a/providers/directory/search_test.go +++ b/providers/directory/search_test.go @@ -281,6 +281,66 @@ objectSid:: AQUAAAAAAAUVAAAAF8sUcR3r8QcekDXQw9wAAA== require.Equal(t, "cn=user1", res.Results[0].Dn) }, }, + { + name: "ldap filter objectSid using AD style with invalid revision", + input: `{ "files": [ "./users.ldif" ] }`, + reader: &dynamictest.Reader{Data: map[string]*dynamic.Config{ + "file:/users.ldif": {Raw: []byte(` +dn: +namingContexts: dc=example_domain_name +subschemaSubentry: cn=schema + +dn: cn=schema +objectClass: top +objectClass: subschema +attributeTypes: ( 1.2.3.4.5.6.7.8 NAME 'objectSid' DESC 'objectSid' EQUALITY activeDirectoryObjectSidMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 ) +`)}, + }}, + test: func(t *testing.T, h ldap.Handler, log *test.Hook, err error) { + require.NoError(t, err) + + rr := ldaptest.NewRecorder() + h.ServeLDAP(rr, ldaptest.NewRequest(0, &ldap.SearchRequest{ + Scope: ldap.ScopeWholeSubtree, + Filter: fmt.Sprintf("(objectSid=S-foo-5-21-1234567890-1234567890-1234567890-1001)"), + })) + res := rr.Message.(*ldap.SearchResponse) + + require.Len(t, res.Results, 0) + require.Len(t, log.Entries, 2) + require.Equal(t, "ldap: filter syntax error: invalid SID 'S-foo-5-21-1234567890-1234567890-1234567890-1001': invalid SID revision value value 'foo' at position: 0", log.Entries[1].Message) + }, + }, + { + name: "ldap filter objectSid using AD style with revision to high", + input: `{ "files": [ "./users.ldif" ] }`, + reader: &dynamictest.Reader{Data: map[string]*dynamic.Config{ + "file:/users.ldif": {Raw: []byte(` +dn: +namingContexts: dc=example_domain_name +subschemaSubentry: cn=schema + +dn: cn=schema +objectClass: top +objectClass: subschema +attributeTypes: ( 1.2.3.4.5.6.7.8 NAME 'objectSid' DESC 'objectSid' EQUALITY activeDirectoryObjectSidMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 ) +`)}, + }}, + test: func(t *testing.T, h ldap.Handler, log *test.Hook, err error) { + require.NoError(t, err) + + rr := ldaptest.NewRecorder() + h.ServeLDAP(rr, ldaptest.NewRequest(0, &ldap.SearchRequest{ + Scope: ldap.ScopeWholeSubtree, + Filter: fmt.Sprintf("(objectSid=S-300-5-21-1234567890-1234567890-1234567890-1001)"), + })) + res := rr.Message.(*ldap.SearchResponse) + + require.Len(t, res.Results, 0) + require.Len(t, log.Entries, 2) + require.Equal(t, "ldap: filter syntax error: invalid SID 'S-300-5-21-1234567890-1234567890-1234567890-1001': SID revision value '5' out of byte range (0-255) at position: 0", log.Entries[1].Message) + }, + }, { name: "ldap filter objectSid using AD style with invalid authId", input: `{ "files": [ "./users.ldif" ] }`, @@ -470,6 +530,82 @@ func TestSearch(t *testing.T) { })) res := rr.Message.(*ldap.SearchResponse) + require.Len(t, res.Results, 1) + }, + }, + { + name: "FilterExtensibleMatch", + input: `{ "files": [ "./users.ldif" ] }`, + reader: &dynamictest.Reader{Data: map[string]*dynamic.Config{ + "file:/users.ldif": {Raw: []byte("dn: cn=user\nuserAccountControl: 512")}, + }}, + test: func(t *testing.T, h ldap.Handler, err error) { + require.NoError(t, err) + + rr := ldaptest.NewRecorder() + h.ServeLDAP(rr, ldaptest.NewRequest(0, &ldap.SearchRequest{ + Scope: ldap.ScopeWholeSubtree, + Filter: "(userAccountControl:1.2.840.113556.1.4.803:=512)", + })) + res := rr.Message.(*ldap.SearchResponse) + + require.Len(t, res.Results, 1) + }, + }, + { + name: "memberOf", + input: `{ "files": [ "./users.ldif" ] }`, + reader: &dynamictest.Reader{Data: map[string]*dynamic.Config{ + "file:/users.ldif": {Raw: []byte("dn: cn=user\n\ndn: cn=group\nmember: cn=user")}, + }}, + test: func(t *testing.T, h ldap.Handler, err error) { + require.NoError(t, err) + + rr := ldaptest.NewRecorder() + h.ServeLDAP(rr, ldaptest.NewRequest(0, &ldap.SearchRequest{ + Scope: ldap.ScopeWholeSubtree, + Filter: "(memberOf=cn=group)", + })) + res := rr.Message.(*ldap.SearchResponse) + + require.Len(t, res.Results, 1) + }, + }, + { + name: "memberOf different cases", + input: `{ "files": [ "./users.ldif" ] }`, + reader: &dynamictest.Reader{Data: map[string]*dynamic.Config{ + "file:/users.ldif": {Raw: []byte("dn: cn=uSEr\n\ndn: cn=group\nmember: cn=UseR")}, + }}, + test: func(t *testing.T, h ldap.Handler, err error) { + require.NoError(t, err) + + rr := ldaptest.NewRecorder() + h.ServeLDAP(rr, ldaptest.NewRequest(0, &ldap.SearchRequest{ + Scope: ldap.ScopeWholeSubtree, + Filter: "(memberOf=cn=GRoup)", + })) + res := rr.Message.(*ldap.SearchResponse) + + require.Len(t, res.Results, 1) + }, + }, + { + name: "memberOf normalize DN", + input: `{ "files": [ "./users.ldif" ] }`, + reader: &dynamictest.Reader{Data: map[string]*dynamic.Config{ + "file:/users.ldif": {Raw: []byte("dn: uid=ff, cn=user\n\ndn: uid=cc,cn=group\nmember: uid=ff,cn=user")}, + }}, + test: func(t *testing.T, h ldap.Handler, err error) { + require.NoError(t, err) + + rr := ldaptest.NewRecorder() + h.ServeLDAP(rr, ldaptest.NewRequest(0, &ldap.SearchRequest{ + Scope: ldap.ScopeWholeSubtree, + Filter: "(memberOf=uid=cc, cn=group)", + })) + res := rr.Message.(*ldap.SearchResponse) + require.Len(t, res.Results, 1) }, }, diff --git a/providers/mail/config.go b/providers/mail/config.go index 86381c7e9..7d3fef8c9 100644 --- a/providers/mail/config.go +++ b/providers/mail/config.go @@ -4,10 +4,11 @@ import ( "bytes" "encoding/json" "fmt" - "gopkg.in/yaml.v3" "mokapi/config/dynamic" "mokapi/smtp" "regexp" + + "gopkg.in/yaml.v3" ) var serverNamePattern = regexp.MustCompile(`^[A-Za-z0-9_-]+$`) @@ -42,9 +43,10 @@ type Server struct { } type Settings struct { - MaxRecipients int `yaml:"maxRecipients" json:"maxRecipients"` - AutoCreateMailbox bool `yaml:"autoCreateMailbox" json:"autoCreateMailbox"` - MaxInboxMails int `yaml:"maxInboxMails" json:"maxInboxMails"` + MaxRecipients int `yaml:"maxRecipients" json:"maxRecipients"` + AutoCreateMailbox bool `yaml:"autoCreateMailbox" json:"autoCreateMailbox"` + MaxInboxMails int `yaml:"maxInboxMails" json:"maxInboxMails"` + AllowUnknownSenders bool `yaml:"allowUnknownSenders" json:"allowUnknownSenders"` } type MailboxConfig struct { @@ -125,7 +127,7 @@ func (c *Config) Parse(_ *dynamic.Config, _ dynamic.Reader) error { func (c *Config) UnmarshalYAML(value *yaml.Node) error { type alias Config tmp := alias(*c) - tmp.Settings = &Settings{AutoCreateMailbox: true} + tmp.Settings = &Settings{AutoCreateMailbox: true, AllowUnknownSenders: true} err := value.Decode(&tmp) *c = Config(tmp) return err @@ -135,7 +137,7 @@ func (c *Config) UnmarshalJSON(b []byte) error { dec := json.NewDecoder(bytes.NewReader(b)) type alias Config tmp := alias(*c) - tmp.Settings = &Settings{AutoCreateMailbox: true} + tmp.Settings = &Settings{AutoCreateMailbox: true, AllowUnknownSenders: true} err := dec.Decode(&tmp) *c = Config(tmp) return err diff --git a/providers/mail/smtp_handler.go b/providers/mail/smtp_handler.go index 6a038595d..cdc6efad9 100644 --- a/providers/mail/smtp_handler.go +++ b/providers/mail/smtp_handler.go @@ -2,12 +2,13 @@ package mail import ( "fmt" - log "github.com/sirupsen/logrus" "mokapi/engine/common" "mokapi/runtime/events" "mokapi/runtime/monitor" "mokapi/smtp" "time" + + log "github.com/sirupsen/logrus" ) type Handler struct { @@ -89,8 +90,10 @@ func (h *Handler) serveMail(rw smtp.ResponseWriter, r *smtp.MailRequest, ctx *sm if len(h.config.Mailboxes) > 0 { if m, ok := h.store.Mailboxes[r.From]; !ok { - h.writeErrorResponse(rw, r, smtp.AddressRejected, fmt.Sprintf("Unknown mailbox %v", r.From)) - return + if !h.AllowUnknownSenders() { + h.writeErrorResponse(rw, r, smtp.AddressRejected, fmt.Sprintf("Unknown mailbox %v", r.From)) + return + } } else if len(m.Username) > 0 && len(ctx.Auth) == 0 { h.writeErrorResponse(rw, r, smtp.AuthRequired, "") return @@ -151,3 +154,10 @@ func (h *Handler) writeRuleResponse(rw smtp.ResponseWriter, r smtp.Request, resp l.Error = response.Message _ = rw.Write(res) } + +func (h *Handler) AllowUnknownSenders() bool { + if h.config.Settings == nil { + return true + } + return h.config.Settings.AllowUnknownSenders +} diff --git a/providers/mail/smtp_handler_test.go b/providers/mail/smtp_handler_test.go index 54e4916ed..bfa0f14a0 100644 --- a/providers/mail/smtp_handler_test.go +++ b/providers/mail/smtp_handler_test.go @@ -3,7 +3,6 @@ package mail_test import ( "context" "encoding/base64" - "github.com/stretchr/testify/require" "mokapi/engine/enginetest" "mokapi/providers/mail" "mokapi/runtime/events/eventstest" @@ -11,6 +10,8 @@ import ( "mokapi/smtp/smtptest" "testing" "time" + + "github.com/stretchr/testify/require" ) func TestHandler_ServeSMTP(t *testing.T) { @@ -71,11 +72,12 @@ func TestHandler_ServeSMTP(t *testing.T) { }, }, { - name: "mail invalid mailbox", + name: "mail unknown mailbox with not allowing unknown mailbox", config: &mail.Config{ Mailboxes: map[string]*mail.MailboxConfig{ "bob@foo.bar": {}, }, + Settings: &mail.Settings{AllowUnknownSenders: false}, }, test: func(t *testing.T, h *mail.Handler, s *mail.Store, _ *eventstest.Handler) { ctx := smtp.NewClientContext(context.Background(), "") @@ -85,6 +87,22 @@ func TestHandler_ServeSMTP(t *testing.T) { require.Equal(t, &exp, r.Result) }, }, + { + name: "mail unknown mailbox and allowing unknown mailbox", + config: &mail.Config{ + Mailboxes: map[string]*mail.MailboxConfig{ + "bob@foo.bar": {}, + }, + Settings: &mail.Settings{AllowUnknownSenders: true}, + }, + test: func(t *testing.T, h *mail.Handler, s *mail.Store, _ *eventstest.Handler) { + ctx := smtp.NewClientContext(context.Background(), "") + r := sendMail(t, h, ctx) + exp := smtp.Ok + exp.Message = "OK" + require.Equal(t, exp, r.Result) + }, + }, { name: "mail valid mailbox", config: &mail.Config{ diff --git a/providers/openapi/handler_response_test.go b/providers/openapi/handler_response_test.go index 6fae702ac..eb0ecd7b5 100644 --- a/providers/openapi/handler_response_test.go +++ b/providers/openapi/handler_response_test.go @@ -305,6 +305,33 @@ func TestHandler_Response_Context(t *testing.T) { require.Equal(t, "response not defined for HTTP status 200\n", rr.Body.String()) }, }, + { + name: "from context and parameter is array ", + opt: openapitest.WithPath("/foo", + openapitest.NewPath(openapitest.WithOperation(http.MethodGet, openapitest.NewOperation( + openapitest.WithQueryParam( + "name", + false, + openapitest.WithParamSchema(schematest.New("array", schematest.WithItems("string"))), + openapitest.WithExplode(true)), + openapitest.WithResponse(http.StatusOK, openapitest.WithContent("application/json", + openapitest.NewContent( + openapitest.WithSchema( + schematest.New("array", schematest.WithItems( + "object", schematest.WithProperty("name", schematest.New("string"))), + ), + ), + ), + ), + ))))), + req: func() *http.Request { + return httptest.NewRequest("get", "http://localhost/foo?name=foo&name=bar", nil) + }, + test: func(t *testing.T, rr *httptest.ResponseRecorder) { + require.Equal(t, http.StatusOK, rr.Code) + require.Equal(t, `[{"name":"foo"},{"name":"bar"},{"name":"bar"},{"name":"foo"},{"name":"foo"}]`, rr.Body.String()) + }, + }, } for _, tc := range testcases { diff --git a/providers/openapi/openapitest/operation.go b/providers/openapi/openapitest/operation.go index a3eae8bac..5a370395b 100644 --- a/providers/openapi/openapitest/operation.go +++ b/providers/openapi/openapitest/operation.go @@ -67,6 +67,12 @@ func WithQueryParam(name string, required bool, opts ...ParamOptions) OperationO } } +func WithExplode(explode bool) ParamOptions { + return func(p *openapi.Parameter) { + p.Explode = &explode + } +} + func WithCookieParam(name string, required bool, opts ...ParamOptions) OperationOptions { return func(o *openapi.Operation) { o.Parameters = append(o.Parameters, &openapi.ParameterRef{ diff --git a/providers/openapi/parameter.go b/providers/openapi/parameter.go index 437c9347e..5d04ad66e 100644 --- a/providers/openapi/parameter.go +++ b/providers/openapi/parameter.go @@ -86,8 +86,8 @@ func (p *Parameter) SetDefaultStyle() { } } -func (p Parameters) Parse(config *dynamic.Config, reader dynamic.Reader) error { - for index, param := range p { +func (p *Parameters) Parse(config *dynamic.Config, reader dynamic.Reader) error { + for index, param := range *p { if err := param.Parse(config, reader); err != nil { return fmt.Errorf("parse parameter index '%v' failed: %w", index, err) } diff --git a/providers/openapi/schema/marshal_xml.go b/providers/openapi/schema/marshal_xml.go index a2bd1c7ca..09b38dfda 100644 --- a/providers/openapi/schema/marshal_xml.go +++ b/providers/openapi/schema/marshal_xml.go @@ -1,7 +1,6 @@ package schema import ( - "bufio" "bytes" "encoding/xml" "fmt" @@ -16,8 +15,7 @@ func marshalXml(i interface{}, r *Schema) ([]byte, error) { return nil, fmt.Errorf("no schema provided") } - var buffer bytes.Buffer - writer := bufio.NewWriter(&buffer) + b := &bytes.Buffer{} var name string if r.Xml != nil && len(r.Xml.Name) > 0 { @@ -36,20 +34,23 @@ func marshalXml(i interface{}, r *Schema) ([]byte, error) { if name == "" { // if no root name is defined we use a default name because for generic tools, the root name isn’t important // so we can improve the user experience to not hit an error - name = "data" + writeXmlStart(b, "data", nil) } if i == nil { - _, _ = writer.WriteString(fmt.Sprintf("<%v>", name, name)) + _, _ = b.WriteString(fmt.Sprintf("<%v>", name, name)) } else { - writeXmlElement(name, i, r, writer) + writeXmlElement(name, i, r, b) } - err := writer.Flush() - return buffer.Bytes(), err + if name == "" { + writeXmlEnd(b, "data") + } + + return b.Bytes(), nil } -func writeXmlElement(name string, i interface{}, s *Schema, w io.Writer) { +func writeXmlElement(name string, i interface{}, s *Schema, b *bytes.Buffer) { wrapped := false attrs := &sortedmap.LinkedHashMap[string, string]{} if s != nil && s.Xml != nil { @@ -72,7 +73,7 @@ func writeXmlElement(name string, i interface{}, s *Schema, w io.Writer) { switch v := i.(type) { case []interface{}: if wrapped { - writeXmlStart(w, name, attrs) + writeXmlStart(b, name, attrs) } var items *Schema if s != nil { @@ -82,20 +83,20 @@ func writeXmlElement(name string, i interface{}, s *Schema, w io.Writer) { if item == nil { continue } - writeXmlElement(name, item, items, w) + writeXmlElement(name, item, items, b) } if wrapped { - writeXmlEnd(w, name) + writeXmlEnd(b, name) } case map[string]interface{}: - writeXmlStart(w, name, attrs) + writeXmlStart(b, name, attrs) for name, v := range v { - writeXmlElement(name, v, nil, w) + writeXmlElement(name, v, nil, b) } - writeXmlEnd(w, name) + writeXmlEnd(b, name) case *sortedmap.LinkedHashMap[string, interface{}]: attrs.Merge(getAttributes(v, s)) - writeXmlStart(w, name, attrs) + writeXmlStart(b, name, attrs) for it := v.Iter(); it.Next(); { if it.Value() == nil { @@ -111,17 +112,17 @@ func writeXmlElement(name string, i interface{}, s *Schema, w io.Writer) { } } - writeXmlElement(propName, it.Value(), prop, w) + writeXmlElement(propName, it.Value(), prop, b) } - writeXmlEnd(w, name) + writeXmlEnd(b, name) default: if i == nil { return } - writeXmlStart(w, name, attrs) - writeXmlContent(w, fmt.Sprintf("%v", i)) - writeXmlEnd(w, name) + writeXmlStart(b, name, attrs) + writeXmlContent(b, fmt.Sprintf("%v", i)) + writeXmlEnd(b, name) } } @@ -151,10 +152,16 @@ func getAttributes(m *sortedmap.LinkedHashMap[string, interface{}], r *Schema) * } func writeXmlStart(w io.Writer, name string, attrs *sortedmap.LinkedHashMap[string, string]) { + if name == "" { + return + } + writeString(w, "<"+name) - for it := attrs.Iter(); it.Next(); { - writeString(w, fmt.Sprintf(" %v=\"%v\"", it.Key(), it.Value())) + if attrs != nil { + for it := attrs.Iter(); it.Next(); { + writeString(w, fmt.Sprintf(" %v=\"%v\"", it.Key(), it.Value())) + } } writeString(w, ">") @@ -165,6 +172,9 @@ func writeXmlContent(w io.Writer, s string) { } func writeXmlEnd(w io.Writer, name string) { + if name == "" { + return + } writeString(w, fmt.Sprintf("", name)) } diff --git a/providers/openapi/schema/marshal_xml_test.go b/providers/openapi/schema/marshal_xml_test.go index 7f6b84592..b8ec81156 100644 --- a/providers/openapi/schema/marshal_xml_test.go +++ b/providers/openapi/schema/marshal_xml_test.go @@ -212,13 +212,26 @@ func TestMarshal_Xml(t *testing.T) { schematest.WithItems("integer"), schematest.WithXml(&schema.Xml{ Name: "foo", - Wrapped: true, // without wrapped results in invalid xml when on root + Wrapped: true, })), test: func(t *testing.T, s string, err error) { require.NoError(t, err) require.Equal(t, `123`, s) }, }, + { + name: "array without XML name", + data: func() interface{} { + return []interface{}{1, 2, 3} + }, + schema: schematest.New("array", + schematest.WithItems("integer", schematest.WithXml(&schema.Xml{Name: "item"})), + ), + test: func(t *testing.T, s string, err error) { + require.NoError(t, err) + require.Equal(t, `123`, s) + }, + }, { name: "array with different item name", data: func() interface{} { @@ -285,6 +298,23 @@ func TestMarshal_Xml(t *testing.T) { require.Equal(t, `bar`, s) }, }, + { + name: "array used in property", + data: func() interface{} { + return map[string]any{"foo": []any{1, 2, 3}} + }, + schema: schematest.New("object", + schematest.WithProperty("foo", + schematest.New("array", + schematest.WithItems("integer"), + ), + ), + ), + test: func(t *testing.T, s string, err error) { + require.NoError(t, err) + require.Equal(t, `123`, s) + }, + }, { name: "object from sortedmap", data: func() interface{} { diff --git a/runtime/monitor/ldap.go b/runtime/monitor/ldap.go index 27927bc46..f1a12a40d 100644 --- a/runtime/monitor/ldap.go +++ b/runtime/monitor/ldap.go @@ -15,7 +15,7 @@ type Ldap struct { func NewLdap() *Ldap { requests := metrics.NewCounterMap( - metrics.WithFQName("ldap", "request_total"), + metrics.WithFQName("ldap", "requests_total"), metrics.WithLabelNames("service", "operation")) errors := metrics.NewCounterMap( metrics.WithFQName("ldap", "search_errors_total"), diff --git a/runtime/monitor/ldap_test.go b/runtime/monitor/ldap_test.go index bf48ede3f..e8300f462 100644 --- a/runtime/monitor/ldap_test.go +++ b/runtime/monitor/ldap_test.go @@ -2,11 +2,19 @@ package monitor import ( "context" - "github.com/stretchr/testify/require" "mokapi/runtime/metrics" "testing" + + "github.com/stretchr/testify/require" ) +func TestLdapLabels(t *testing.T) { + l := NewLdap() + require.Equal(t, "ldap_search_errors_total", l.Errors.Info().String()) + require.Equal(t, "ldap_requests_total", l.RequestCounter.Info().String()) + require.Equal(t, "ldap_request_timestamp", l.LastRequest.Info().String()) +} + func TestLdap_Metrics_Bind(t *testing.T) { l := NewLdap() l.RequestCounter.WithLabel("service_a", "bind").Add(1) diff --git a/runtime/runtime_ldap.go b/runtime/runtime_ldap.go index fa54cecc2..0f0096cdf 100644 --- a/runtime/runtime_ldap.go +++ b/runtime/runtime_ldap.go @@ -2,8 +2,6 @@ package runtime import ( "context" - "github.com/blevesearch/bleve/v2" - log "github.com/sirupsen/logrus" "mokapi/config/dynamic" "mokapi/config/static" "mokapi/engine/common" @@ -15,6 +13,9 @@ import ( "sort" "sync" "time" + + "github.com/blevesearch/bleve/v2" + log "github.com/sirupsen/logrus" ) type LdapStore struct { @@ -185,7 +186,13 @@ func (h *ldapHandler) ServeLDAP(rw ldap.ResponseWriter, r *ldap.Request) { r.Context = context.WithValue(r.Context, "time", time.Now()) h.next.ServeLDAP(rw, r) +} + +func (h *ldapHandler) Unbind(ctx context.Context) { + ctx = monitor.NewLdapContext(ctx, h.ldap) + ctx = context.WithValue(ctx, "time", time.Now()) + h.next.Unbind(ctx) } func IsLdapConfig(c *dynamic.Config) (*directory.Config, bool) { diff --git a/schema/json/generator/date_test.go b/schema/json/generator/date_test.go index f99cf28af..959dd8f07 100644 --- a/schema/json/generator/date_test.go +++ b/schema/json/generator/date_test.go @@ -1,13 +1,25 @@ package generator import ( - "github.com/brianvoe/gofakeit/v6" - "github.com/stretchr/testify/require" "mokapi/schema/json/schema/schematest" "testing" + "time" + + "github.com/brianvoe/gofakeit/v6" + "github.com/stretchr/testify/require" ) func TestStringDate(t *testing.T) { + // tests depends on current year so without this, all tests will break in next year + isDateString := func(t *testing.T, s any) { + _, err := time.Parse("2006-01-02", s.(string)) + require.NoError(t, err) + } + isDateTimeString := func(t *testing.T, s any) { + _, err := time.Parse(time.RFC3339, s.(string)) + require.NoError(t, err) + } + testcases := []struct { name string req *Request @@ -21,7 +33,7 @@ func TestStringDate(t *testing.T) { }, test: func(t *testing.T, v interface{}, err error) { require.NoError(t, err) - require.Equal(t, "2020-08-26", v) + isDateString(t, v) }, }, { @@ -31,7 +43,7 @@ func TestStringDate(t *testing.T) { }, test: func(t *testing.T, v interface{}, err error) { require.NoError(t, err) - require.Equal(t, "2020-08-26", v) + isDateString(t, v) }, }, { @@ -41,7 +53,7 @@ func TestStringDate(t *testing.T) { }, test: func(t *testing.T, v interface{}, err error) { require.NoError(t, err) - require.Equal(t, "2020-08-26", v) + isDateString(t, v) }, }, { @@ -51,7 +63,7 @@ func TestStringDate(t *testing.T) { }, test: func(t *testing.T, v interface{}, err error) { require.NoError(t, err) - require.Equal(t, "2020-08-26", v) + isDateString(t, v) }, }, { @@ -61,7 +73,7 @@ func TestStringDate(t *testing.T) { }, test: func(t *testing.T, v interface{}, err error) { require.NoError(t, err) - require.Equal(t, "2020-08-26", v) + isDateString(t, v) }, }, { @@ -71,7 +83,7 @@ func TestStringDate(t *testing.T) { }, test: func(t *testing.T, v interface{}, err error) { require.NoError(t, err) - require.Equal(t, "2020-08-26", v) + isDateString(t, v) }, }, { @@ -81,7 +93,7 @@ func TestStringDate(t *testing.T) { }, test: func(t *testing.T, v interface{}, err error) { require.NoError(t, err) - require.Equal(t, "2020-08-26", v) + isDateString(t, v) }, }, { @@ -91,7 +103,7 @@ func TestStringDate(t *testing.T) { }, test: func(t *testing.T, v interface{}, err error) { require.NoError(t, err) - require.Equal(t, "2020-08-26", v) + isDateString(t, v) }, }, { @@ -101,7 +113,7 @@ func TestStringDate(t *testing.T) { }, test: func(t *testing.T, v interface{}, err error) { require.NoError(t, err) - require.Equal(t, "2020-08-26", v) + isDateString(t, v) }, }, { @@ -111,7 +123,7 @@ func TestStringDate(t *testing.T) { }, test: func(t *testing.T, v interface{}, err error) { require.NoError(t, err) - require.Equal(t, "1970-08-26", v) + isDateString(t, v) }, }, { @@ -124,7 +136,9 @@ func TestStringDate(t *testing.T) { }, test: func(t *testing.T, v interface{}, err error) { require.NoError(t, err) - require.Equal(t, map[string]any{"activeFrom": "2020-08-26T07:02:11Z", "inactiveFrom": "2029-08-30T11:01:28Z"}, v) + m := v.(map[string]interface{}) + isDateTimeString(t, m["activeFrom"]) + isDateTimeString(t, m["inactiveFrom"]) }, }, { @@ -137,7 +151,9 @@ func TestStringDate(t *testing.T) { }, test: func(t *testing.T, v interface{}, err error) { require.NoError(t, err) - require.Equal(t, map[string]any{"publishedFrom": "2020-08-26T07:02:11Z", "publishedUntil": "2029-08-30T11:01:28Z"}, v) + m := v.(map[string]interface{}) + isDateTimeString(t, m["publishedFrom"]) + isDateTimeString(t, m["publishedUntil"]) }, }, } diff --git a/schema/json/generator/file_test.go b/schema/json/generator/file_test.go index ad3636493..b38451f27 100644 --- a/schema/json/generator/file_test.go +++ b/schema/json/generator/file_test.go @@ -1,13 +1,21 @@ package generator import ( - "github.com/brianvoe/gofakeit/v6" - "github.com/stretchr/testify/require" "mokapi/schema/json/schema/schematest" "testing" + "time" + + "github.com/brianvoe/gofakeit/v6" + "github.com/stretchr/testify/require" ) func TestFile(t *testing.T) { + // tests depends on current year so without this, all tests will break in next year + isDateString := func(t *testing.T, s any) { + _, err := time.Parse("2006-01-02", s.(string)) + require.NoError(t, err) + } + testcases := []struct { name string req *Request @@ -41,7 +49,7 @@ func TestFile(t *testing.T) { }, test: func(t *testing.T, v interface{}, err error) { require.NoError(t, err) - require.Equal(t, "2020-08-26", v) + isDateString(t, v) }, }, { @@ -62,7 +70,7 @@ func TestFile(t *testing.T) { }, test: func(t *testing.T, v interface{}, err error) { require.NoError(t, err) - require.Equal(t, "2020-08-26", v) + isDateString(t, v) }, }, } diff --git a/schema/json/generator/it_test.go b/schema/json/generator/it_test.go index 1618f7f9a..76f5d3b92 100644 --- a/schema/json/generator/it_test.go +++ b/schema/json/generator/it_test.go @@ -1,10 +1,12 @@ package generator import ( - "github.com/brianvoe/gofakeit/v6" - "github.com/stretchr/testify/require" "mokapi/schema/json/schema/schematest" "testing" + "time" + + "github.com/brianvoe/gofakeit/v6" + "github.com/stretchr/testify/require" ) func TestIt(t *testing.T) { @@ -99,6 +101,12 @@ func TestStringHash(t *testing.T) { } func TestUser(t *testing.T) { + // tests depends on current year so without this, all tests will break in next year + isDateTimeString := func(t *testing.T, s any) { + _, err := time.Parse(time.RFC3339, s.(string)) + require.NoError(t, err) + } + testcases := []struct { name string req *Request @@ -141,7 +149,7 @@ func TestUser(t *testing.T) { }, test: func(t *testing.T, v interface{}, err error) { require.NoError(t, err) - require.Equal(t, "2024-08-26T07:02:11Z", v) + isDateTimeString(t, v) }, }, { diff --git a/schema/json/generator/person.go b/schema/json/generator/person.go index 5179a0d9a..fc3795d7c 100644 --- a/schema/json/generator/person.go +++ b/schema/json/generator/person.go @@ -345,7 +345,7 @@ func fakeDateInPastWithMinYear(r *Request, minYear int) (any, error) { year := gofakeit.Number(1940, time.Now().Year()) year = int(math.Max(float64(year), float64(minYear))) month := gofakeit.Number(1, 12) - if year == time.Now().Year() { + if year == now.Year() { month = gofakeit.Number(1, int(now.Month())) } diff --git a/schema/json/generator/person_test.go b/schema/json/generator/person_test.go index 59cd3c008..85b70cc76 100644 --- a/schema/json/generator/person_test.go +++ b/schema/json/generator/person_test.go @@ -1,13 +1,21 @@ package generator import ( - "github.com/brianvoe/gofakeit/v6" - "github.com/stretchr/testify/require" "mokapi/schema/json/schema/schematest" "testing" + "time" + + "github.com/brianvoe/gofakeit/v6" + "github.com/stretchr/testify/require" ) func TestPerson(t *testing.T) { + // tests depends on current year so without this, all tests will break in next year + isDateString := func(t *testing.T, s any) { + _, err := time.Parse("2006-01-02", s.(string)) + require.NoError(t, err) + } + testcases := []struct { name string req *Request @@ -265,7 +273,7 @@ func TestPerson(t *testing.T) { }, test: func(t *testing.T, v interface{}, err error) { require.NoError(t, err) - require.Equal(t, "1970-08-26", v) + isDateString(t, v) }, }, { @@ -276,7 +284,7 @@ func TestPerson(t *testing.T) { }, test: func(t *testing.T, v interface{}, err error) { require.NoError(t, err) - require.Equal(t, "1970-08-26", v) + isDateString(t, v) }, }, { diff --git a/schema/json/generator/resolve.go b/schema/json/generator/resolve.go index 7979b1b58..93837cd8e 100644 --- a/schema/json/generator/resolve.go +++ b/schema/json/generator/resolve.go @@ -239,6 +239,16 @@ func useFromContext(r *Request) (*faker, bool) { return v, nil }), true } + if arr, ok := v.([]any); ok { + if len(arr) > 0 { + if _, err := p.Parse(arr[0]); err == nil { + return newFaker(func() (any, error) { + i := r.g.rand.Intn(len(arr)) + return arr[i], nil + }), true + } + } + } } } return nil, false diff --git a/server/server_mail.go b/server/server_mail.go index 274d7c65e..a38be992c 100644 --- a/server/server_mail.go +++ b/server/server_mail.go @@ -2,7 +2,6 @@ package server import ( "fmt" - log "github.com/sirupsen/logrus" "mokapi/config/dynamic" engine "mokapi/engine/common" "mokapi/imap" @@ -13,6 +12,8 @@ import ( "mokapi/smtp" "net" "sync" + + log "github.com/sirupsen/logrus" ) type MailServer interface { @@ -75,10 +76,7 @@ func (m *MailManager) startServers(cfg *runtime.MailInfo) error { } h := cfg.Handler(m.app.Monitor.Mail, m.eventEmitter, m.app.Events) for _, server := range cfg.Servers { - _, port, err := net.SplitHostPort(server.Host) - if err != nil { - return err - } + _, port, _ := net.SplitHostPort(server.Host) switch server.Protocol { case "smtp": diff --git a/server/server_mail_test.go b/server/server_mail_test.go index f41de2b7f..11c0aff04 100644 --- a/server/server_mail_test.go +++ b/server/server_mail_test.go @@ -2,7 +2,6 @@ package server_test import ( "fmt" - "github.com/stretchr/testify/require" "mokapi/config/dynamic" "mokapi/config/dynamic/dynamictest" "mokapi/config/static" @@ -15,6 +14,8 @@ import ( "mokapi/smtp/smtptest" "mokapi/try" "testing" + + "github.com/stretchr/testify/require" ) func TestSmtp(t *testing.T) { diff --git a/smtp/conn.go b/smtp/conn.go index f1aa74199..d74635d34 100644 --- a/smtp/conn.go +++ b/smtp/conn.go @@ -5,14 +5,15 @@ import ( "crypto/tls" "encoding/base64" "fmt" - "github.com/pkg/errors" - log "github.com/sirupsen/logrus" "io" "net" "net/textproto" "runtime/debug" "strings" "syscall" + + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" ) type conn struct { @@ -338,9 +339,16 @@ func (c *conn) serveStartTls(param string) { write(c.tpc, 550, EnhancedStatusCode{5, 0, 0}, "Handshake error") return } + + // Remove the old raw TCP connection + delete(c.server.activeConn, c.conn) + c.conn = tlsConn c.tpc = textproto.NewConn(c.conn) c.reset() + + // Add the new TLS connection + c.server.activeConn[c.conn] = c.ctx } func (c *conn) reset() { diff --git a/sortedmap/sortedmap.go b/sortedmap/sortedmap.go index 2ca323f26..7af39fa08 100644 --- a/sortedmap/sortedmap.go +++ b/sortedmap/sortedmap.go @@ -11,6 +11,8 @@ import ( // LinkedHashMap defines the iteration ordering by the order // in which keys were inserted into the map type LinkedHashMap[K comparable, V any] struct { + KeyNormalizer func(K) K + pairs map[interface{}]*pair[K, V] list *list.List } @@ -26,11 +28,15 @@ func NewLinkedHashMap() *LinkedHashMap[string, interface{}] { } func (m *LinkedHashMap[K, V]) Set(key K, value V) { + nk := key + if m.KeyNormalizer != nil { + nk = m.KeyNormalizer(key) + } m.ensureInit() - p, ok := m.pairs[key] + p, ok := m.pairs[nk] if !ok { - p = &pair[K, V]{key: key, value: value} - m.pairs[key] = p + p = &pair[K, V]{key: nk, value: value} + m.pairs[nk] = p p.element = m.list.PushBack(p) } else { p.value = value @@ -46,7 +52,11 @@ func (m *LinkedHashMap[K, V]) Len() int { func (m *LinkedHashMap[K, V]) Get(key K) (V, bool) { if m.pairs != nil { - p, ok := m.pairs[key] + nk := key + if m.KeyNormalizer != nil { + nk = m.KeyNormalizer(key) + } + p, ok := m.pairs[nk] if ok { return p.value, true } @@ -58,16 +68,24 @@ func (m *LinkedHashMap[K, V]) Del(key K) { if m.pairs == nil { return } - p, ok := m.pairs[key] + nk := key + if m.KeyNormalizer != nil { + nk = m.KeyNormalizer(key) + } + p, ok := m.pairs[nk] if !ok { return } - delete(m.pairs, key) + delete(m.pairs, nk) m.list.Remove(p.element) } func (m *LinkedHashMap[K, V]) Lookup(key K) V { - v, _ := m.Get(key) + nk := key + if m.KeyNormalizer != nil { + nk = m.KeyNormalizer(key) + } + v, _ := m.Get(nk) return v } diff --git a/webui/.env b/webui/.env index 5811dbc3e..9cb0340c3 100644 --- a/webui/.env +++ b/webui/.env @@ -1 +1,2 @@ +VITE_USE_DEMO=true VITE_DASHBOARD=true \ No newline at end of file diff --git a/webui/.env.dashboard b/webui/.env.dashboard new file mode 100644 index 000000000..a9dbf867f --- /dev/null +++ b/webui/.env.dashboard @@ -0,0 +1,3 @@ +VITE_WEBSITE=false +VITE_USE_DEMO=false +VITE_DASHBOARD=true \ No newline at end of file diff --git a/webui/.env.website b/webui/.env.website index e23563bb1..963c77c36 100644 --- a/webui/.env.website +++ b/webui/.env.website @@ -1 +1,3 @@ +VITE_WEBSITE=true +VITE_USE_DEMO=true VITE_DASHBOARD=false \ No newline at end of file diff --git a/webui/.gitignore b/webui/.gitignore index 082f33fd6..7382fc04c 100644 --- a/webui/.gitignore +++ b/webui/.gitignore @@ -31,3 +31,4 @@ test-results/ playwright-report/ src/assets/docs public/sitemap.xml +public/demo/ diff --git a/webui/e2e/Dashboard/http/books.spec.ts b/webui/e2e/Dashboard/http/books.spec.ts index ee72bbc5b..22c16a616 100644 --- a/webui/e2e/Dashboard/http/books.spec.ts +++ b/webui/e2e/Dashboard/http/books.spec.ts @@ -52,14 +52,14 @@ test.describe('Visit Books API', () => { let cells = path.methods.locator('tbody tr').nth(0).getByRole('cell') await expect(cells.nth(0)).toHaveText('GET', {ignoreCase: false}) await expect(cells.nth(0).locator('span')).toHaveClass('badge operation get') - await expect(cells.nth(1)).toHaveText('Add a new pet to the store') - await expect(cells.nth(2)).toHaveText('listBooks') + await expect(cells.nth(1)).toHaveText('listBooks') + await expect(cells.nth(2)).toHaveText('Get books from the store') cells = path.methods.locator('tbody tr').nth(1).getByRole('cell') await expect(cells.nth(0)).toHaveText('POST', {ignoreCase: false}) await expect(cells.nth(0).locator('span')).toHaveClass('badge operation post') - await expect(cells.nth(1)).toHaveText('Add a new book') - await expect(cells.nth(2)).toHaveText('addBook') + await expect(cells.nth(1)).toHaveText('addBook') + await expect(cells.nth(2)).toHaveText('Add a new book') await test.step('visit method post', async () => { await path.clickOperation('POST') diff --git a/webui/e2e/Dashboard/kafka/cluster.spec.ts b/webui/e2e/Dashboard/kafka/cluster.spec.ts index cc3d44056..ed3b71508 100644 --- a/webui/e2e/Dashboard/kafka/cluster.spec.ts +++ b/webui/e2e/Dashboard/kafka/cluster.spec.ts @@ -12,15 +12,14 @@ test('Visit Kafka cluster "Kafka World"', async ({ page }) => { await open() await tabs.kafka.click() - await page.getByRole('table', { name: 'Kafka Clusters' }).getByText(cluster.name).click() + await page.getByRole('table', { name: 'Kafka Clusters' }).getByRole('link', { name: cluster.name }).click() }) await test.step('Check info section', async () => { const info = page.getByRole('region', { name: "Info" }) await expect(info.getByLabel('Name')).toHaveText(cluster.name) await expect(info.getByLabel('Version')).toHaveText(cluster.version) - await expect(info.getByLabel('Contact').getByTitle('mokapi - Website')).toHaveText(cluster.contact.name) - const mailto = info.getByLabel('Contact').getByTitle('Send email to mokapi') + const mailto = info.getByLabel('Contact').getByTitle('info@mokapi.io') await expect(mailto).toBeVisible() await expect(mailto).toHaveAttribute("href", /^mailto:/) await expect(info.getByLabel('Type of API')).toHaveText('Kafka') @@ -28,7 +27,7 @@ test('Visit Kafka cluster "Kafka World"', async ({ page }) => { }) await test.step('Check broker section', async () => { - const brokers = useTable(page.getByRole('region', { name: "Brokers" }).getByRole('table', { name: 'Cluster Brokers' }), ['Name', 'Host', 'Tags', 'Description']) + const brokers = useTable(page.getByRole('region', { name: "Brokers" }).getByRole('table', { name: 'Brokers' }), ['Name', 'Host', 'Description', 'Tags']) const broker = brokers.getRow(1) await expect(broker.getCellByName('Name')).toHaveText(cluster.brokers[0].name) await expect(broker.getCellByName('Host')).toHaveText(cluster.brokers[0].url) @@ -37,7 +36,7 @@ test('Visit Kafka cluster "Kafka World"', async ({ page }) => { }) await test.step('Check topic section', async () => { - const table = page.getByRole('region', { name: "Topics" }).getByRole('table', { name: 'Cluster Topics' }) + const table = page.getByRole('region', { name: "Topics" }).getByRole('table', { name: 'Topics' }) await expect(table).toBeVisible() const topics = useKafkaTopics(table) await topics.testTopic(0, cluster.topics[0]) @@ -45,7 +44,7 @@ test('Visit Kafka cluster "Kafka World"', async ({ page }) => { }) await test.step('Check groups section', async () => { - const table = page.getByRole('region', { name: "Groups" }).getByRole('table', { name: 'Cluster Groups' }) + const table = page.getByRole('region', { name: "Groups" }).getByRole('table', { name: 'Groups' }) await expect(table).toBeVisible() const groups = useKafkaGroups(table) await groups.testGroup(0, cluster.groups[0]) @@ -56,11 +55,11 @@ test('Visit Kafka cluster "Kafka World"', async ({ page }) => { const configs = useTable(page.getByRole('region', { name: "Configs" }).getByRole('table', { name: 'Configs' }), ['URL', 'Provider', 'Last Update']) const config = configs.getRow(1) await expect(config.getCellByName('URL')).toHaveText('https://www.example.com/foo/bar/communication/service/asyncapi.json') - await expect(config.getCellByName('Provider')).toHaveText('http') + await expect(config.getCellByName('Provider')).toHaveText('HTTP') await expect(config.getCellByName('Last Update')).toHaveText(formatDateTime('2023-02-15T08:49:25.482366+01:00')) }) - await useKafkaMessages(page).test(page.getByRole('region', { name: "Recent Messages" }).getByRole('table', { name: 'Cluster Messages' })) + await useKafkaMessages(page).test(page.getByRole('region', { name: "Recent Messages" }).getByRole('table', { name: 'Recent Messages' })) }) test('Visit Kafka cluster config file', async ({ page, context }) => { @@ -74,7 +73,7 @@ test('Visit Kafka cluster config file', async ({ page, context }) => { await page.getByRole('table', { name: 'Configs' }).getByText('https://www.example.com/foo/bar/communication/service/asyncapi.json').click() await expect(page.getByLabel('URL')).toHaveText('https://www.example.com/foo/bar/communication/service/asyncapi.json') - await expect(page.getByLabel('Provider')).toHaveText('http') + await expect(page.getByLabel('Provider')).toHaveText('HTTP') await expect(page.getByLabel('Last Modified')).toHaveText(formatDateTime('2023-02-15T08:49:25.482366+01:00')) const { test: testSourceView } = useSourceView(page.getByRole('region', { name: 'Content' })) diff --git a/webui/e2e/Dashboard/kafka/topic.order.spec.ts b/webui/e2e/Dashboard/kafka/topic.order.spec.ts index 620b951ba..f72caddcd 100644 --- a/webui/e2e/Dashboard/kafka/topic.order.spec.ts +++ b/webui/e2e/Dashboard/kafka/topic.order.spec.ts @@ -16,10 +16,10 @@ test('Visit Kafka topic mokapi.shop.products', async ({ page, context }) => { await open() await tabs.kafka.click() - await page.getByRole('table', { name: 'Kafka Clusters' }).getByText(cluster.name).click() + await page.getByRole('table', { name: 'Clusters' }).getByText(cluster.name).click() await expect(page.getByRole('region', { name: "Info" })).toBeVisible() - await page.getByRole('table', { name: 'Cluster Topics' }).getByText(topic.name).click() + await page.getByRole('table', { name: 'Topics' }).getByText(topic.name).click() }) await test.step('Check info section"', async () => { @@ -31,7 +31,7 @@ test('Visit Kafka topic mokapi.shop.products', async ({ page, context }) => { await expect(info.getByLabel('Description')).toHaveText(topic.description) }) - await useKafkaMessages(page).test(page.getByRole('table', { name: 'Topic Messages' }), false) + await useKafkaMessages(page).test(page.getByRole('table', { name: 'Recent Messages' }), false) const tabList = page.getByRole('region', { name: 'Topic Data' }).getByRole('tablist') await test.step('Check partition"', async () => { @@ -59,7 +59,7 @@ test('Visit Kafka topic mokapi.shop.products', async ({ page, context }) => { await expect(configs.getByLabel('Name')).toHaveText(topic.messageConfigs[0].name) await expect(configs.getByLabel('Summary')).toHaveText(topic.messageConfigs[0].summary) await expect(configs.getByLabel('Description')).toHaveText(topic.messageConfigs[0].description) - await expect(configs.getByLabel('Content Type')).toHaveText(topic.messageConfigs[0].contentType) + await expect(configs.getByLabel('Message Content Type')).toHaveText(topic.messageConfigs[0].contentType) diff --git a/webui/e2e/Dashboard/kafka/topic.userSignedUp.spec.ts b/webui/e2e/Dashboard/kafka/topic.userSignedUp.spec.ts index 227a14bf7..911d9ba41 100644 --- a/webui/e2e/Dashboard/kafka/topic.userSignedUp.spec.ts +++ b/webui/e2e/Dashboard/kafka/topic.userSignedUp.spec.ts @@ -16,7 +16,7 @@ test('Visit Kafka topic mokapi.shop.userSignedUp', async ({ page, context }) => await page.getByRole('table', { name: 'Kafka Clusters' }).getByText(cluster.name).click() await expect(page.getByRole('region', { name: "Info" })).toBeVisible() - await page.getByRole('table', { name: 'Cluster Topics' }).getByText(topic.name).click() + await page.getByRole('table', { name: 'Topics' }).getByText(topic.name).click() }) await test.step('Check info section"', async () => { @@ -60,7 +60,7 @@ test('Visit Kafka topic mokapi.shop.userSignedUp', async ({ page, context }) => await expect(configs.getByLabel('Summary')).not.toBeVisible() await expect(configs.getByLabel('Description')).not.toBeVisible() - await expect(configs.getByLabel('Content Type')).toHaveText(topic.messageConfigs[1].contentType) + await expect(configs.getByLabel('Message Content Type')).toHaveText(topic.messageConfigs[1].contentType) const { test: testSourceView } = useSourceView(configs.getByRole('tabpanel', { name: 'Value' })) await testSourceView({ diff --git a/webui/e2e/Dashboard/mail/testserver.spec.ts b/webui/e2e/Dashboard/mail/testserver.spec.ts index 39d64ea10..dfef572bb 100644 --- a/webui/e2e/Dashboard/mail/testserver.spec.ts +++ b/webui/e2e/Dashboard/mail/testserver.spec.ts @@ -29,16 +29,17 @@ test('Visit Mail Testserver', async ({ page }) => { const servers = useTable(table, ['Name', 'Host', 'Protocol', 'Description']) let row = servers.getRow(1) - await expect(row.getCellByName('Name')).toHaveText('smtp') - await expect(row.getCellByName('Host')).toHaveText('localhost:8025') - await expect(row.getCellByName('Protocol')).toHaveText('smtp') - await expect(row.getCellByName('Description')).toHaveText("Mokapi's SMTP Server") - - row = servers.getRow(2) await expect(row.getCellByName('Name')).toHaveText('imap') await expect(row.getCellByName('Host')).toHaveText('localhost:8030') await expect(row.getCellByName('Protocol')).toHaveText('imap') await expect(row.getCellByName('Description')).toHaveText("Mokapi's IMAP Server") + + row = servers.getRow(2) + await expect(row.getCellByName('Name')).toHaveText('smtp') + await expect(row.getCellByName('Host')).toHaveText('localhost:8025') + await expect(row.getCellByName('Protocol')).toHaveText('smtp') + await expect(row.getCellByName('Description')).toHaveText("Mokapi's SMTP Server") + }) await test.step('Check mailboxes', async () => { @@ -129,7 +130,7 @@ test('Visit Mail Testserver', async ({ page }) => { await expect(body.getByRole('img')).toHaveAttribute('src', /\/attachments\/icon.png$/) const attachments = page.getByRole('region', { name: 'Attachments' }) - const foo = attachments.getByRole('listitem', { name: 'foo.txt' }) + const foo = attachments.getByRole('link', { name: 'foo.txt' }) await expect(foo.getByLabel('Disposition')).toHaveText('attachment') await expect(foo.getByLabel('Size')).toHaveText('34.06 kB') @@ -139,7 +140,7 @@ test('Visit Mail Testserver', async ({ page }) => { ]) await expect(download.suggestedFilename()).toBe('foo.txt') - const icon = attachments.getByRole('listitem', { name: 'icon.png' }) + const icon = attachments.getByRole('link', { name: 'icon.png' }) await expect(icon.getByLabel('Disposition')).toHaveText('inline') await expect(icon.getByLabel('Size')).toHaveText('372 B') }) diff --git a/webui/e2e/components/dashboard.ts b/webui/e2e/components/dashboard.ts index 2a9032be1..e0c00105f 100644 --- a/webui/e2e/components/dashboard.ts +++ b/webui/e2e/components/dashboard.ts @@ -10,10 +10,10 @@ export function useDashboard(page: Page) { export function useDashboardTabs(page: Page) { return { overview: page.getByRole('link', { name: 'Overview' }), - http: page.getByRole('link', { name: 'HTTP' }), - kafka: page.getByRole('link', { name: 'Kafka' }), - mail: page.getByRole('link', { name: 'Mail' }), - ldap: page.getByRole('link', { name: 'LDAP' }), - configs: page.getByRole('link', { name: 'Configs' }), + http: page.getByRole('link', { name: 'HTTP', exact: true }), + kafka: page.getByRole('link', { name: 'Kafka', exact: true }), + mail: page.getByRole('link', { name: 'Mail', exact: true }), + ldap: page.getByRole('link', { name: 'LDAP', exact: true }), + configs: page.getByRole('link', { name: 'Configs', exact: true }), } } \ No newline at end of file diff --git a/webui/e2e/components/kafka.ts b/webui/e2e/components/kafka.ts index e34d3ff99..968adbb5b 100644 --- a/webui/e2e/components/kafka.ts +++ b/webui/e2e/components/kafka.ts @@ -128,7 +128,7 @@ export function useKafkaMessages(page: Page) { await expect(page.getByText('Producer Id', { exact: true })).not.toBeVisible({ timeout: 100 }) await expect(page.getByText('Producer Epoch', { exact: true })).not.toBeVisible({ timeout: 100 }) await expect(page.getByText('Sequence Number', { exact: true })).not.toBeVisible({ timeout: 100 }) - await expect(page.getByLabel('Content Type', { exact: true })).toHaveText('application/json') + await expect(page.getByRole('region', { name: 'Meta' }).getByLabel('Content Type', { exact: true })).toHaveText('application/json') await expect(page.getByLabel('Key Type', { exact: true })).toHaveText('string') await expect(page.getByLabel('Time', { exact: true })).toHaveText(formatDateTime('2023-02-13T09:49:25.482366+01:00')) @@ -151,7 +151,7 @@ export function useKafkaMessages(page: Page) { await expect(page.getByLabel('Producer Id', { exact: true })).toHaveText('3') await expect(page.getByLabel('Producer Epoch', { exact: true })).toHaveText('1') await expect(page.getByLabel('Sequence Number', { exact: true })).toHaveText('1') - await expect(page.getByLabel('Content Type', { exact: true })).toHaveText('application/json') + await expect(page.getByRole('region', { name: 'Meta' }).getByLabel('Content Type', { exact: true })).toHaveText('application/json') await expect(page.getByLabel('Key Type', { exact: true })).toHaveText('string') await expect(page.getByLabel('Time', { exact: true })).toHaveText(formatDateTime('2023-02-13T09:49:25.482366+01:00')) diff --git a/webui/e2e/dashboard-demo/dashboard.spec.ts b/webui/e2e/dashboard-demo/dashboard.spec.ts new file mode 100644 index 000000000..074f27a73 --- /dev/null +++ b/webui/e2e/dashboard-demo/dashboard.spec.ts @@ -0,0 +1,55 @@ +import { test, expect } from '../models/fixture-dashboard' +import { getCellByColumnName } from '../helpers/table' + +test.use({ colorScheme: 'light' }) +// reset storage state +test.use({ storageState: { cookies: [], origins: [] } }); + +test('Visit Dashboard Demo Overview', async ({ page }) => { + await page.goto('/dashboard-demo'); + + await test.step('Verify app metrics', async () => { + const main = page.locator('main') + + await expect(main.locator('a[aria-current="page"]')).toHaveText('Overview'); + + await expect(main.getByLabel('Uptime Since')).not.toHaveText('-') + await expect(main.getByLabel('Started at')).not.toHaveText('-') + }) + + await test.step('Verify Swagger Petstore is in the HTTP table', async() => { + const table = page.getByRole('table', { name: 'HTTP APIs' }) + await expect(table).toBeVisible() + + const row = table.getByRole('row').filter({ hasText: 'Swagger Petstore' }) + await expect(row).toBeVisible() + await expect(await getCellByColumnName(table, 'Requests / Errors', row)).toHaveText('12 / 0') + }) + + await test.step('Verify Kafka Order Service is in the Kafka Clusters table', async() => { + const table = page.getByRole('table', { name: 'Kafka Clusters' }) + await expect(table).toBeVisible() + + const row = table.getByRole('row').filter({ hasText: 'Kafka Order Service API' }) + await expect(row).toBeVisible() + await expect(await getCellByColumnName(table, 'Messages', row)).toHaveText('2') + }) + + await test.step('Verify Mail Server is in the Mail table', async() => { + const table = page.getByRole('table', { name: 'Mail Servers' }) + await expect(table).toBeVisible() + + const row = table.getByRole('row').filter({ hasText: 'Mail Server' }) + await expect(row).toBeVisible() + await expect(await getCellByColumnName(table, 'Messages', row)).toHaveText('2') + }) + + await test.step('Verify LDAP Testserver is in the LDAP table', async() => { + const table = page.getByRole('table', { name: 'LDAP Servers' }) + await expect(table).toBeVisible() + + const row = table.getByRole('row').filter({ hasText: 'HR Employee Directory' }) + await expect(row).toBeVisible() + await expect(await getCellByColumnName(table, 'Requests', row)).toHaveText('10') + }) +}) \ No newline at end of file diff --git a/webui/e2e/dashboard-demo/kafka.spec.ts b/webui/e2e/dashboard-demo/kafka.spec.ts new file mode 100644 index 000000000..8fce5b7e8 --- /dev/null +++ b/webui/e2e/dashboard-demo/kafka.spec.ts @@ -0,0 +1,197 @@ +import { test, expect } from '../models/fixture-dashboard' +import { getCellByColumnName } from '../helpers/table' + +test.use({ colorScheme: 'light' }) +// reset storage state +test.use({ storageState: { cookies: [], origins: [] } }); + +test('Visit Kafka Order Service', async ({ page }) => { + + await page.goto('/dashboard-demo'); + await page.getByText('Kafka Order Service API').click(); + + await test.step('Verify service info', async () => { + + await expect(page.getByLabel('Name')).toHaveText('Kafka Order Service API'); + await expect(page.getByLabel('Version')).toHaveText('1.0.0'); + await expect(page.getByLabel('Description')).toHaveText('An API to process customer orders and notify about order status updates using Kafka.') + + }); + + await test.step('Verify Brokers', async () => { + + await expect(page.getByRole('region', { name: 'Brokers' })).toBeVisible(); + const table = page.getByRole('table', { name: 'Brokers' }); + const rows = table.locator('tbody tr'); + await expect(rows).toHaveCount(1); + await expect(await getCellByColumnName(table, 'Name', rows.nth(0))).toHaveText('development'); + await expect(await getCellByColumnName(table, 'Host', rows.nth(0))).toHaveText('localhost:9092'); + await expect(await getCellByColumnName(table, 'Description', rows.nth(0))).toHaveText('Local development Kafka broker.'); + + }); + + await test.step('Verify Topics', async () => { + + await expect(page.getByRole('region', { name: 'Topics' })).toBeVisible(); + const table = page.getByRole('table', { name: 'Topics' }); + const rows = table.locator('tbody tr'); + await expect(rows).toHaveCount(1); + await expect(await getCellByColumnName(table, 'Name', rows.nth(0))).toHaveText('order-topic'); + await expect(await getCellByColumnName(table, 'Description', rows.nth(0))).toHaveText('The Kafka topic for order events.'); + await expect(await getCellByColumnName(table, 'Last Message', rows.nth(0))).not.toHaveText('-'); + await expect(await getCellByColumnName(table, 'Messages', rows.nth(0))).toHaveText('2'); + + }); + + await test.step('Verify Groups', async () => { + + await expect(page.getByRole('region', { name: 'Groups' })).toBeVisible(); + const table = page.getByRole('table', { name: 'Groups' }); + const rows = table.locator('tbody tr'); + await expect(rows).toHaveCount(1); + await expect(await getCellByColumnName(table, 'Name', rows.nth(0))).toHaveText('order-status-group-100'); + await expect(await getCellByColumnName(table, 'State', rows.nth(0))).toHaveText('Stable'); + await expect(await getCellByColumnName(table, 'Protocol', rows.nth(0))).toHaveText('RoundRobinAssigner'); + await expect(await getCellByColumnName(table, 'Coordinator', rows.nth(0))).toHaveText('localhost:9092'); + await expect(await getCellByColumnName(table, 'Leader', rows.nth(0))).toHaveText(/^producer/); + const members = await getCellByColumnName(table, 'Members', rows.nth(0)) + await expect(members).toHaveText(/^producer/); + + await members.hover(); + const tooltip = page.getByRole('tooltip') + await expect(tooltip).toBeVisible(); + await expect(tooltip.getByLabel('Address')).not.toBeEmpty(); + await expect(tooltip.getByLabel('Client Software')).toHaveText('-'); + await expect(tooltip.getByLabel('Last Heartbeat')).not.toBeEmpty(); + await expect(tooltip.getByLabel('Topics')).toHaveText('order-topic'); + + await rows.nth(0).getByRole('cell').nth(0).click(); + const dialog = page.getByRole('dialog', { name: 'Group Details' }); + await expect(dialog).toBeVisible(); + await expect(dialog.getByLabel('Name')).toHaveText('order-status-group-100'); + await expect(dialog.getByLabel('State')).toHaveText('Stable'); + await expect(dialog.getByLabel('Protocol')).toHaveText('RoundRobinAssigner'); + await expect(dialog.getByLabel('Coordinator')).toHaveText('localhost:9092'); + await expect(dialog.getByLabel('Leader')).toHaveText(/^producer/); + + await dialog.getByRole('tab', { name: 'Topics' }).click(); + const topics = dialog.getByRole('table', { name: 'Topics' }); + await expect(await getCellByColumnName(topics, 'Topic')).toHaveText('order-topic'); + + await dialog.getByRole('tab', { name: 'Members' }).click(); + const membersPanel = dialog.getByRole('tabpanel', { name: 'Members' }); + await expect(membersPanel).toBeVisible(); + await expect(membersPanel.getByRole('tab', { name: /^producer/ })).toHaveAttribute('aria-selected', 'true'); + await expect(membersPanel.getByLabel('Address')).not.toBeEmpty(); + await expect(membersPanel.getByLabel('Client Software')).toHaveText('-'); + await expect(membersPanel.getByLabel('Heartbeat')).not.toBeEmpty(); + const memberPartitions = membersPanel.getByRole('table', { name: 'Member Partitions' }) + await expect(await getCellByColumnName(memberPartitions, 'Topic')).toHaveText('order-topic'); + await expect(await getCellByColumnName(memberPartitions, 'Partitions')).toHaveText('0'); + + await dialog.getByRole('button', { name: 'Close' }).click(); + + }); + + await test.step('Verify Configs', async () => { + const table = page.getByRole('table', { name: 'Configs' }); + await expect(await getCellByColumnName(table, 'URL')).toContainText('/webui/scripts/dashboard-demo/demo-configs/asyncapi.yaml'); + await expect(await getCellByColumnName(table, 'Provider')).toHaveText('File'); + }); + + await test.step('Verify Recent Messages', async () => { + + const region = page.getByRole('region', { name: 'Recent Messages' }); + await expect(region).toBeVisible(); + + const table = region.getByRole('table', { name: 'Recent Messages' }); + const rows = table.locator('tbody tr'); + await expect(rows).toHaveCount(2); + await expect(await getCellByColumnName(table, 'Key', rows.nth(0))).toHaveText('a914817b-c5f0-433e-8280-1cd2fe44234e'); + await expect(await getCellByColumnName(table, 'Value', rows.nth(0))).toContainText('{"orderId":"a914817b-c5f0-433e-8280-1cd2fe44234e","productId":"2a'); + await expect(await getCellByColumnName(table, 'Topic', rows.nth(0))).toHaveText('order-topic'); + await expect(await getCellByColumnName(table, 'Time', rows.nth(0))).not.toBeEmpty(); + + }) + + await test.step('Visit Kafka Topic', async () => { + + await page.getByRole('table', { name: 'Topics' }).getByText('order-topic').click(); + await expect(page.getByLabel('Topic', { exact: true })).toHaveText('order-topic'); + await expect(page.getByLabel('Cluster')).toHaveText('Kafka Order Service API'); + await expect(page.getByLabel('Cluster')).toHaveAttribute('href'); + await expect(page.getByLabel('Description')).toHaveText('The Kafka topic for order events.'); + + await expect(page.getByLabel('Type of API')).toHaveText('Kafka'); + + await test.step('Verify Message', async () => { + + await page.getByRole('table', { name: 'Recent Messages' }).locator('tbody tr').getByRole('link', { name: 'a914817b-c5f0-433e-8280-1cd2fe44234e' }).click(); + await expect(page.getByLabel('Kafka Key')).toHaveText('a914817b-c5f0-433e-8280-1cd2fe44234e'); + await expect(page.getByLabel('Kafka Topic')).toHaveText('order-topic'); + await expect(page.getByLabel('Kafka Topic')).toHaveAttribute('href', '/dashboard-demo/kafka/service/Kafka%20Order%20Service%20API/topic/order-topic'); + await expect(page.getByLabel('Offset')).toHaveText('1'); + await expect(page.getByRole('region', { name: 'Meta' }).getByLabel('Content Type')).toHaveText('application/json'); + await expect(page.getByLabel('Key Type')).toHaveText('-'); + await expect(page.getByLabel('Key Type')).not.toBeEmpty(); + + const value = page.getByRole('region', { name: 'Value' }); + await expect(value.getByLabel('Content Type')).toHaveText('application/json'); + await expect(value.getByLabel('Lines of Code')).toHaveText('8 lines'); + await expect(value.getByLabel('Size of Code')).toHaveText('249 B'); + await expect(value.getByLabel('Content', { exact: true })).toContainText('"orderId": "a914817b-c5f0-433e-8280-1cd2fe44234e",') + + await page.goBack(); + + }); + + await test.step('Verify Partitions', async () => { + + await page.getByRole('tab', { name: 'Partitions' }).click(); + const table = page.getByRole('table', { name: 'Partitions' }); + + const rows = table.locator('tbody tr'); + await expect(rows).toHaveCount(1); + await expect(await getCellByColumnName(table, 'ID')).toHaveText('0'); + await expect(await getCellByColumnName(table, 'Leader')).toHaveText('development (localhost:9092)'); + await expect(await getCellByColumnName(table, 'Start Offset')).toHaveText('0'); + await expect(await getCellByColumnName(table, 'Offset')).toHaveText('2'); + await expect(await getCellByColumnName(table, 'Segments')).toHaveText('1'); + + }); + + await test.step('Verify Groups', async () => { + + await page.getByRole('tab', { name: 'Groups' }).click(); + const table = page.getByRole('table', { name: 'Groups' }); + + const rows = table.locator('tbody tr'); + await expect(rows).toHaveCount(1); + await expect(await getCellByColumnName(table, 'Name')).toHaveText('order-status-group-100'); + await expect(await getCellByColumnName(table, 'State')).toHaveText('Stable'); + await expect(await getCellByColumnName(table, 'Protocol')).toHaveText('RoundRobinAssigner'); + await expect(await getCellByColumnName(table, 'Coordinator')).toHaveText('localhost:9092'); + await expect(await getCellByColumnName(table, 'Leader')).toHaveText(/^producer/); + await expect(await getCellByColumnName(table, 'Members')).toContainText(/^producer/); + await expect(await getCellByColumnName(table, 'Lag')).toHaveText('0'); + + }); + + await test.step('Verify Configs', async () => { + + await page.getByRole('tab', { name: 'Configs' }).click(); + const configs = page.getByRole('tabpanel', { name: 'Configs' }); + await expect(configs.getByLabel('Name')).toHaveText('OrderCreatedEvent'); + await expect(configs.getByLabel('Title')).toHaveText('Order Created Event'); + await expect(configs.getByLabel('Summary')).toHaveText('Notification that a new order has been created.'); + await expect(configs.getByLabel('Message Content Type', { exact: true })).toHaveText('application/json'); + const value = configs.getByRole('tabpanel', { name: 'Value'}) + await expect(value.getByLabel('Lines of Code').nth(0)).toHaveText('44 lines'); + await expect(value.getByLabel('Size of Code').nth(0)).toHaveText('877 B'); + await expect(value.getByRole('region', { name: 'Content' }).nth(0)).toContainText(`"type": "object", + "properties": {`); + + }); + + }); +}); \ No newline at end of file diff --git a/webui/e2e/dashboard-demo/ldap.spec.ts b/webui/e2e/dashboard-demo/ldap.spec.ts new file mode 100644 index 000000000..c85eee356 --- /dev/null +++ b/webui/e2e/dashboard-demo/ldap.spec.ts @@ -0,0 +1,127 @@ +import { test, expect } from '../models/fixture-dashboard' +import { getCellByColumnName } from '../helpers/table' + +test.use({ colorScheme: 'light' }) +// reset storage state +test.use({ storageState: { cookies: [], origins: [] } }); + +test('Visit LDAP Testserver', async ({ page }) => { + + await page.goto('/dashboard-demo'); + await page.getByRole('cell').getByText('HR Employee Directory').click(); + + await test.step('Verify service info', async () => { + + await expect(page.getByLabel('Name')).toHaveText('HR Employee Directory'); + await expect(page.getByLabel('Version')).toHaveText('1.0.0'); + await expect(page.getByLabel('Contact')).not.toBeVisible(); + await expect(page.getByLabel('Type of API')).toHaveText('LDAP'); + await expect(page.getByLabel('Description')).toHaveText('LDAP server for internal employee contact information.'); + + }); + + await test.step('Verify Servers', async () => { + + const region = page.getByRole('region', { name: 'Servers' }); + const table = region.getByRole('table', { name: 'Servers' }); + const rows = table.locator('tbody tr'); + await expect(rows).toHaveCount(1); + + await expect(await getCellByColumnName(table, 'Address', rows.nth(0))).toHaveText(':8389'); + + }); + + await test.step('Verify Configs', async () => { + + const table = page.getByRole('table', { name: 'Configs' }); + await expect(await getCellByColumnName(table, 'URL')).toContainText('/webui/scripts/dashboard-demo/demo-configs/ldap.yaml'); + await expect(await getCellByColumnName(table, 'Provider')).toHaveText('File'); + + }); + + await test.step('Verify Recent Requests', async () => { + const table = page.getByRole('table', { name: 'Recent Requests' }); + let rows = table.locator('tbody tr'); + + // Unbind + await expect(await getCellByColumnName(table, 'Operation', rows.nth(0))).toHaveText('Unbind'); + await expect(await getCellByColumnName(table, 'DN', rows.nth(0))).toBeEmpty(); + await expect(await getCellByColumnName(table, 'Criteria', rows.nth(0))).toBeEmpty(); + await expect(await getCellByColumnName(table, 'Status', rows.nth(0))).toHaveText('-'); + await expect(await getCellByColumnName(table, 'Time', rows.nth(0))).not.toBeEmpty(); + await expect(await getCellByColumnName(table, 'Duration', rows.nth(0))).not.toBeEmpty(); + + // Search useAccountControl + await expect(await getCellByColumnName(table, 'Operation', rows.nth(1))).toHaveText('Search'); + await expect(await getCellByColumnName(table, 'DN', rows.nth(1))).toHaveText('dc=hr,dc=example,dc=com'); + await expect(await getCellByColumnName(table, 'Criteria', rows.nth(1))).toHaveText('(userAccountControl:1.2.840.113556.1.4.803:=512)'); + await expect(await getCellByColumnName(table, 'Status', rows.nth(1))).toHaveText('Success'); + await expect(await getCellByColumnName(table, 'Time', rows.nth(1))).not.toBeEmpty(); + await expect(await getCellByColumnName(table, 'Duration', rows.nth(1))).not.toBeEmpty(); + + // Delete + await expect(await getCellByColumnName(table, 'Operation', rows.nth(2))).toHaveText('Delete'); + await expect(await getCellByColumnName(table, 'DN', rows.nth(2))).toHaveText('uid=ctaylor,ou=people,dc=hr,dc=example,dc=com'); + await expect(await getCellByColumnName(table, 'Criteria', rows.nth(2))).toHaveText(''); + await expect(await getCellByColumnName(table, 'Status', rows.nth(2))).toHaveText('Success'); + await expect(await getCellByColumnName(table, 'Time', rows.nth(2))).not.toBeEmpty(); + await expect(await getCellByColumnName(table, 'Duration', rows.nth(2))).not.toBeEmpty(); + + // ModifyDN + await expect(await getCellByColumnName(table, 'Operation', rows.nth(3))).toHaveText('ModifyDN'); + await expect(await getCellByColumnName(table, 'DN', rows.nth(3))).toHaveText('uid=cbrown,ou=people,dc=hr,dc=example,dc=com'); + await expect(await getCellByColumnName(table, 'Criteria', rows.nth(3))).toHaveText('uid=ctaylor'); + await expect(await getCellByColumnName(table, 'Status', rows.nth(3))).toHaveText('Success'); + await expect(await getCellByColumnName(table, 'Time', rows.nth(3))).not.toBeEmpty(); + await expect(await getCellByColumnName(table, 'Duration', rows.nth(3))).not.toBeEmpty(); + + // Compare + await expect(await getCellByColumnName(table, 'Operation', rows.nth(4))).toHaveText('Compare'); + await expect(await getCellByColumnName(table, 'DN', rows.nth(4))).toHaveText('uid=bmiller,ou=people,dc=hr,dc=example,dc=com'); + await expect(await getCellByColumnName(table, 'Criteria', rows.nth(4))).toHaveText('telephoneNumber == +1 555 123 9876'); + await expect(await getCellByColumnName(table, 'Status', rows.nth(4))).toHaveText('CompareTrue'); + await expect(await getCellByColumnName(table, 'Time', rows.nth(4))).not.toBeEmpty(); + await expect(await getCellByColumnName(table, 'Duration', rows.nth(4))).not.toBeEmpty(); + + // Modify + await expect(await getCellByColumnName(table, 'Operation', rows.nth(5))).toHaveText('Modify'); + await expect(await getCellByColumnName(table, 'DN', rows.nth(5))).toHaveText('uid=bmiller,ou=people,dc=hr,dc=example,dc=com'); + await expect(await getCellByColumnName(table, 'Criteria', rows.nth(5))).toHaveText('add telephoneNumber'); + await expect(await getCellByColumnName(table, 'Status', rows.nth(5))).toHaveText('Success'); + await expect(await getCellByColumnName(table, 'Time', rows.nth(5))).not.toBeEmpty(); + await expect(await getCellByColumnName(table, 'Duration', rows.nth(5))).not.toBeEmpty(); + + // Add + await expect(await getCellByColumnName(table, 'Operation', rows.nth(6))).toHaveText('Add'); + await expect(await getCellByColumnName(table, 'DN', rows.nth(6))).toHaveText('uid=cbrown,ou=people,dc=hr,dc=example,dc=com'); + await expect(await getCellByColumnName(table, 'Criteria', rows.nth(6))).toBeEmpty(); + await expect(await getCellByColumnName(table, 'Status', rows.nth(6))).toHaveText('Success'); + await expect(await getCellByColumnName(table, 'Time', rows.nth(6))).not.toBeEmpty(); + await expect(await getCellByColumnName(table, 'Duration', rows.nth(6))).not.toBeEmpty(); + + // Search memberOf + await expect(await getCellByColumnName(table, 'Operation', rows.nth(7))).toHaveText('Search'); + await expect(await getCellByColumnName(table, 'DN', rows.nth(7))).toHaveText('dc=hr,dc=example,dc=com'); + await expect(await getCellByColumnName(table, 'Criteria', rows.nth(7))).toHaveText('(memberOf=cn=Sales,ou=departments,dc=hr,dc=example,dc=com)'); + await expect(await getCellByColumnName(table, 'Status', rows.nth(7))).toHaveText('Success'); + await expect(await getCellByColumnName(table, 'Time', rows.nth(7))).not.toBeEmpty(); + await expect(await getCellByColumnName(table, 'Duration', rows.nth(7))).not.toBeEmpty(); + + // Search uid + await expect(await getCellByColumnName(table, 'Operation', rows.nth(8))).toHaveText('Search'); + await expect(await getCellByColumnName(table, 'DN', rows.nth(8))).toHaveText('dc=hr,dc=example,dc=com'); + await expect(await getCellByColumnName(table, 'Criteria', rows.nth(8))).toHaveText('(uid=ajohnson)'); + await expect(await getCellByColumnName(table, 'Status', rows.nth(8))).toHaveText('Success'); + await expect(await getCellByColumnName(table, 'Time', rows.nth(8))).not.toBeEmpty(); + await expect(await getCellByColumnName(table, 'Duration', rows.nth(8))).not.toBeEmpty(); + + // Bind + await expect(await getCellByColumnName(table, 'Operation', rows.nth(9))).toHaveText('Bind'); + await expect(await getCellByColumnName(table, 'DN', rows.nth(9))).toHaveText('dc=hr,dc=example,dc=com'); + await expect(await getCellByColumnName(table, 'Criteria', rows.nth(9))).toHaveText(''); + await expect(await getCellByColumnName(table, 'Status', rows.nth(9))).toHaveText('Success'); + await expect(await getCellByColumnName(table, 'Time', rows.nth(9))).not.toBeEmpty(); + await expect(await getCellByColumnName(table, 'Duration', rows.nth(9))).not.toBeEmpty(); + + }); +}); \ No newline at end of file diff --git a/webui/e2e/dashboard-demo/mail.spec.ts b/webui/e2e/dashboard-demo/mail.spec.ts new file mode 100644 index 000000000..20e30a134 --- /dev/null +++ b/webui/e2e/dashboard-demo/mail.spec.ts @@ -0,0 +1,174 @@ +import { test, expect } from '../models/fixture-dashboard' +import { getCellByColumnName } from '../helpers/table' + +test.use({ colorScheme: 'light' }) +// reset storage state +test.use({ storageState: { cookies: [], origins: [] } }); + +test('Visit Mail Server', async ({ page }) => { + + await page.goto('/dashboard-demo'); + await page.getByRole('cell').getByText(/^Mail Server$/).click(); + + await test.step('Verify service info', async () => { + + const region = page.getByRole('region', { name: 'Info' }); + await expect(region.getByLabel('Name')).toHaveText('Mail Server'); + await expect(region.getByLabel('Version')).toHaveText('1.0.0'); + await expect(region.getByLabel('Contact').getByRole('link').nth(0)).toHaveText('Support Team'); + await expect(region.getByLabel('Contact').getByRole('link').nth(0)).toHaveAttribute('href', 'https://support.example.com'); + await expect(region.getByLabel('Contact').getByRole('link').nth(1)).toHaveAttribute('title', 'support@example.com'); + await expect(region.getByLabel('Contact').getByRole('link').nth(1)).toHaveAttribute('href', 'mailto:support@example.com'); + await expect(region.getByLabel('Type of API')).toHaveText('Mail'); + await expect(region.getByLabel('Description')).toHaveText('Configuration for the internal mail server.'); + + }); + + await test.step('Verify Servers', async () => { + + const region = page.getByRole('tabpanel', { name: 'Servers' }); + const table = region.getByRole('table', { name: 'Servers' }); + const rows = table.locator('tbody tr'); + await expect(rows).toHaveCount(2); + + await expect(await getCellByColumnName(table, 'Name', rows.nth(0))).toHaveText('imap'); + await expect(await getCellByColumnName(table, 'Host', rows.nth(0))).toHaveText('localhost:8143'); + await expect(await getCellByColumnName(table, 'Protocol', rows.nth(0))).toHaveText('imap'); + await expect(await getCellByColumnName(table, 'Description', rows.nth(0))).toHaveText('IMAP mail server for accessing mails'); + + await expect(await getCellByColumnName(table, 'Name', rows.nth(1))).toHaveText('smtp'); + await expect(await getCellByColumnName(table, 'Host', rows.nth(1))).toHaveText('localhost:8025'); + await expect(await getCellByColumnName(table, 'Protocol', rows.nth(1))).toHaveText('smtp'); + await expect(await getCellByColumnName(table, 'Description', rows.nth(1))).toHaveText('Primary outgoing mail server'); + + }); + + await test.step('Verify Mailboxes', async () => { + + await page.getByRole('tab', { name: 'Mailboxes' }).click(); + + const region = page.getByRole('tabpanel', { name: 'Mailboxes' }); + const table = region.getByRole('table', { name: 'Mailboxes' }); + const rows = table.locator('tbody tr'); + await expect(rows).toHaveCount(3 /*TODO 2*/); + + await expect(await getCellByColumnName(table, 'Mailbox', rows.nth(0))).toHaveText('alice.johnson@example.com'); + await expect(await getCellByColumnName(table, 'Username', rows.nth(0))).toHaveText('alice.johnson'); + await expect(await getCellByColumnName(table, 'Password', rows.nth(0))).toHaveText('anothersecretpassword456'); + await expect(await getCellByColumnName(table, 'Description', rows.nth(0))).toHaveText('Configuration for Alice Johnson\'s.'); + await expect(await getCellByColumnName(table, 'Mails', rows.nth(0))).toHaveText('1'); + + await expect(await getCellByColumnName(table, 'Mailbox', rows.nth(1))).toHaveText('bob.miller@example.com'); + await expect(await getCellByColumnName(table, 'Username', rows.nth(1))).toHaveText('bob.miller'); + await expect(await getCellByColumnName(table, 'Password', rows.nth(1))).toHaveText('mysecretpassword123'); + await expect(await getCellByColumnName(table, 'Description', rows.nth(1))).toHaveText('Configuration for Bob Miller\'s.'); + await expect(await getCellByColumnName(table, 'Mails', rows.nth(1))).toHaveText('1'); + + await test.step('Verify Mailbox bob.miller@example.com', async () => { + + await rows.nth(1).click(); + await expect(page.getByLabel('Mailbox Name')).toHaveText('bob.miller@example.com'); + await expect(page.getByLabel('Service', { exact: true })).toHaveText('Mail Server'); + await expect(page.getByLabel('Service', { exact: true }).getByRole('link')).toHaveAttribute('href', '/dashboard-demo/mail/service/Mail%20Server'); + await expect(page.getByLabel('Username')).toHaveText('bob.miller'); + await expect(page.getByLabel('Password')).toHaveText('mysecretpassword123'); + + const folders = page.getByRole('table', { name: 'Folders' }); + await expect(await getCellByColumnName(folders, 'Name')).toHaveText('INBOX'); + + const mails = page.getByRole('table', { name: 'Mails' }); + await expect(await getCellByColumnName(mails, 'Subject')).toHaveText('Reset Your Password'); + await expect(await getCellByColumnName(mails, 'From')).toHaveText('zzz@example.com'); + await expect(await getCellByColumnName(mails, 'To')).toHaveText('Bob Miller '); + await expect(await getCellByColumnName(mails, 'Date')).not.toBeEmpty(); + + await test.step('Verify Mail Reset Your Password', async () => { + + await mails.locator('tbody tr').click(); + await expect(page.getByLabel('Subject')).toHaveText('Reset Your Password'); + await expect(page.getByLabel('Service', { exact: true })).toHaveText('Mail Server'); + await expect(page.getByLabel('Service', { exact: true }).getByRole('link')).toHaveAttribute('href', '/dashboard-demo/mail/service/Mail%20Server'); + await expect(page.getByLabel('From')).not.toBeEmpty(); + await expect(page.getByLabel('From')).toHaveText('zzz@example.com'); + await expect(page.getByLabel('To', { exact: true })).toHaveText('Bob Miller '); + + const body = page.getByRole('region', { name: 'Body' }); + await expect(body.getByRole('heading')).toHaveText('Reset Your Password'); + await expect(body).toContainText('Hello John Doe,'); + + await expect(page.getByLabel('Content-Type')).toHaveText('text/html; charset=utf-8'); + await expect(page.getByLabel('Encoding')).toHaveText('quoted-printable'); + await expect(page.getByLabel('Message-ID')).not.toBeEmpty(); + }); + }); + + await test.step('Verify Mailbox alice.johnson@example.com', async () => { + + await page.getByText('Mail Server').click(); + await page.getByRole('tab', { name: 'Mailboxes' }).click(); + await page.getByRole('table', { name: 'Mailboxes' }).getByText('alice.johnson@example.com').click(); + + await expect(page.getByLabel('Mailbox Name')).toHaveText('alice.johnson@example.com'); + await expect(page.getByLabel('Service', { exact: true })).toHaveText('Mail Server'); + await expect(page.getByLabel('Service', { exact: true }).getByRole('link')).toHaveAttribute('href', '/dashboard-demo/mail/service/Mail%20Server'); + await expect(page.getByLabel('Username')).toHaveText('alice.johnson'); + await expect(page.getByLabel('Password')).toHaveText('anothersecretpassword456'); + + const folders = page.getByRole('table', { name: 'Folders' }); + await expect(await getCellByColumnName(folders, 'Name')).toHaveText('INBOX'); + + const mails = page.getByRole('table', { name: 'Mails' }); + await expect(await getCellByColumnName(mails, 'Subject')).toHaveText('Check Out Our New Arrivals!'); + await expect(await getCellByColumnName(mails, 'From')).toHaveText('Bob Miller '); + await expect(await getCellByColumnName(mails, 'To')).toHaveText('Alice Johnson '); + await expect(await getCellByColumnName(mails, 'Date')).not.toBeEmpty(); + + await test.step('Verify Mail Check Out Our New Arrivals!', async () => { + + await mails.locator('tbody tr').click(); + await expect(page.getByLabel('Subject')).toHaveText('Check Out Our New Arrivals!'); + await expect(page.getByLabel('Service', { exact: true })).toHaveText('Mail Server'); + await expect(page.getByLabel('Service', { exact: true }).getByRole('link')).toHaveAttribute('href', '/dashboard-demo/mail/service/Mail%20Server'); + await expect(page.getByLabel('From')).not.toBeEmpty(); + await expect(page.getByLabel('From')).toHaveText('Bob Miller '); + await expect(page.getByLabel('To', { exact: true })).toHaveText('Alice Johnson '); + + const body = page.getByRole('region', { name: 'Body' }); + await expect(body.getByRole('heading', { level: 1 })).toHaveText('New Arrivals Just Landed!'); + await expect(body).toContainText('Fresh styles,'); + + const attachments = page.getByRole('region', { name: 'Attachments '}); + await expect(attachments).toBeVisible(); + await expect(attachments.getByRole('link', { name: 'headerimg' })).toHaveAttribute('href', /\/demo\/header.jpg$/) + await expect(attachments.getByRole('link', { name: 'product1' })).toHaveAttribute('href', /\/demo\/product1.jpg$/) + await expect(attachments.getByRole('link', { name: 'product2' })).toHaveAttribute('href', /\/demo\/product2.jpg$/) + await expect(attachments.getByRole('link', { name: 'product3' })).toHaveAttribute('href', /\/demo\/product3.jpg$/) + + await expect(page.getByLabel('Content-Type')).toHaveText('text/html'); + await expect(page.getByLabel('Encoding')).not.toBeVisible(); + await expect(page.getByLabel('Message-ID')).not.toBeEmpty(); + }); + }); + + }); + + await test.step('Verify Settings', async () => { + + await page.getByText('Mail Server').click(); + await page.getByRole('tab', { name: 'Settings' }).click(); + + const region = page.getByRole('tabpanel', { name: 'Settings' }); + await expect(region.getByLabel('Max Recipients')).toHaveText('unlimited'); + await expect(region.getByLabel('Auto Create Mailbox')).toHaveText('true'); + + }); + + await test.step('Verify Configs', async () => { + + await page.getByRole('tab', { name: 'Configs' }).click(); + const table = page.getByRole('table', { name: 'Configs' }); + await expect(await getCellByColumnName(table, 'URL')).toContainText('/webui/scripts/dashboard-demo/demo-configs/mail.yaml'); + await expect(await getCellByColumnName(table, 'Provider')).toHaveText('File'); + + }); +}); \ No newline at end of file diff --git a/webui/e2e/dashboard-demo/petstore.spec.ts b/webui/e2e/dashboard-demo/petstore.spec.ts new file mode 100644 index 000000000..3471cf431 --- /dev/null +++ b/webui/e2e/dashboard-demo/petstore.spec.ts @@ -0,0 +1,198 @@ +import { test, expect } from '../models/fixture-dashboard' +import { getCellByColumnName } from '../helpers/table' + +test.use({ colorScheme: 'light' }) +// reset storage state +test.use({ storageState: { cookies: [], origins: [] } }); + +test('Visit Petstore Demo', async ({ page }) => { + + await page.goto('/dashboard-demo'); + await page.getByText('Swagger Petstore').click(); + + await test.step('Verify service info', async () => { + await expect(page.getByLabel('Name')).toHaveText('Swagger Petstore'); + await expect(page.getByLabel('Version')).toHaveText('1.0.0'); + await expect(page.getByLabel('Version')).toHaveText('1.0.0'); + await expect(page.getByLabel('Contact').getByRole('link')).toHaveAttribute('href', 'mailto:apiteam@swagger.io'); + await expect(page.getByLabel('Type of API')).toHaveText('HTTP'); + + const description = page.getByLabel('Description'); + await expect(description).toContainText('This is a sample server Petstore server.'); + await expect(description.getByRole('link', { name: 'http://swagger.io'})).toBeVisible(); + }); + + await test.step('Verify Servers', async () => { + const table = page.getByRole('table', { name: 'Servers'}); + const url = await getCellByColumnName(table, 'Url'); + await expect(url).toHaveText('http://petstore.swagger.io/v2'); + }); + + await test.step('Verify Paths', async () => { + const region = page.getByRole('region', { name: 'Paths' }); + await expect(region).toBeVisible(); + + await expect(region.getByRole('checkbox', { name: 'All' })).toBeChecked(); + await expect(region.getByRole('checkbox', { name: 'pet' })).toBeChecked(); + await expect(region.getByRole('checkbox', { name: 'store' })).toBeChecked(); + await expect(region.getByRole('checkbox', { name: 'user' })).toBeChecked(); + + const table = page.getByRole('table', { name: 'Paths' }); + await expect(table.locator('tbody tr')).toHaveCount(14); + const row = table.locator('tbody tr').nth(0); + await expect(await getCellByColumnName(table, 'Path', row)).toHaveText('/pet'); + await expect(await getCellByColumnName(table, 'Operations', row)).toHaveText('POST PUT'); + await expect(await getCellByColumnName(table, 'Requests / Errors', row)).toHaveText('1 / 0'); + + await region.getByRole('checkbox', { name: 'store' }).click(); + await region.getByRole('checkbox', { name: 'user' }).click(); + await expect(region.getByRole('checkbox', { name: 'All' })).not.toBeChecked(); + await expect(region.getByRole('checkbox', { name: 'pet' })).toBeChecked(); + await expect(region.getByRole('checkbox', { name: 'store' })).not.toBeChecked(); + await expect(region.getByRole('checkbox', { name: 'user' })).not.toBeChecked(); + + await expect(table.locator('tbody tr')).toHaveCount(5); + + const deprecatedRow = table.locator('tbody tr').nth(4); + await expect(deprecatedRow.getByRole('cell').nth(0)).toHaveText('Deprecated') + }); + + await test.step('Verify Configs', async () => { + const table = page.getByRole('table', { name: 'Configs' }); + const rows = table.locator('tbody tr'); + await expect(await getCellByColumnName(table, 'URL', rows.nth(0))).toContainText('/webui/scripts/dashboard-demo/demo-configs/petstore.yaml'); + await expect(await getCellByColumnName(table, 'Provider', rows.nth(0))).toHaveText('File'); + + await expect(await getCellByColumnName(table, 'URL', rows.nth(1))).toContainText('/webui/scripts/dashboard-demo/demo-configs/z.petstore.fix.yaml'); + await expect(await getCellByColumnName(table, 'Provider', rows.nth(1))).toHaveText('File'); + }); + + await test.step('Verify Recent Requests', async () => { + const table = page.getByRole('table', { name: 'Recent Requests' }); + let rows = table.locator('tbody tr'); + + await expect(rows).toHaveCount(12); + await expect(await getCellByColumnName(table, 'Method', rows.nth(10))).toHaveText('POST'); + await expect(await getCellByColumnName(table, 'URL', rows.nth(10))).toHaveText('http://localhost/v2/pet'); + await expect(await getCellByColumnName(table, 'Status Code', rows.nth(10))).toHaveText('200 OK'); + + await expect(await getCellByColumnName(table, 'Method', rows.nth(11))).toHaveText('GET'); + await expect(await getCellByColumnName(table, 'URL', rows.nth(11))).toHaveText('http://localhost/v2/pet/10'); + await expect(await getCellByColumnName(table, 'Status Code', rows.nth(11))).toHaveText('200 OK'); + + const filter = page.getByRole('region', { name: 'Recent Requests'}).getByRole('button', { name: 'Filter' }) + await filter.click(); + const dialog = page.getByRole('dialog', { name: 'Filter' }); + await expect(dialog).toBeVisible(); + await dialog.getByRole('checkbox', { name: 'Method' }).click(); + await expect(rows).toHaveCount(9); + + await dialog.getByRole('checkbox', { name: 'Method' }).click(); + await dialog.getByRole('checkbox', { name: 'GET' }).click(); + await expect(rows).toHaveCount(0); + + await dialog.getByRole('checkbox', { name: 'Method' }).click(); + await expect(rows).toHaveCount(12); + + await dialog.getByRole('checkbox', { name: 'URL' }).click(); + await dialog.getByRole('textbox', { name: 'URL filter'}).fill('/pet/10'); + await expect(rows).toHaveCount(1); + + await dialog.getByRole('button', { name: 'close' }).click(); + await expect(filter).toHaveAccessibleName('Filter (1 active filter)') + }); + + await test.step('Verify /pet/{petId}', async () => { + await page.getByText('/pet/{petId}', { exact: true }).click() + + await expect(page.getByLabel('Path', { exact: true })).toHaveText('/pet/{petId}') + await expect(page.getByLabel('Service', { exact: true })).toHaveText('Swagger Petstore') + await expect(page.getByLabel('Service', { exact: true }).getByRole('link')).toHaveAttribute('href', '/dashboard-demo/http/services/Swagger%20Petstore') + + const region = page.getByRole('region', { name: 'Methods' }); + await expect(region).toBeVisible(); + + const table = page.getByRole('table', { name: 'Methods' }); + const rows = table.locator('tbody tr'); + await expect(rows).toHaveCount(3); + await expect(await getCellByColumnName(table, 'Method', rows.nth(0))).toHaveText('DELETE'); + await expect(await getCellByColumnName(table, 'Operation ID', rows.nth(0))).toHaveText('deletePet'); + await expect(await getCellByColumnName(table, 'Summary', rows.nth(0))).toHaveText(' Deletes a pet'); + + const requests = page.getByRole('table', { name: 'Recent Requests' }); + await expect(requests.locator('tbody tr')).toHaveCount(1); + }); + + await test.step('Verify DELETE /pet/{petId}', async () => { + await page.getByText('DELETE', { exact: true }).click() + + await expect(page.getByLabel('Operation', { exact: true })).toHaveText('DELETE /pet/{petId}'); + await expect(page.getByLabel('Operation ID')).toHaveText('deletePet'); + await expect(page.getByLabel('Service', { exact: true })).toHaveText('Swagger Petstore'); + await expect(page.getByLabel('Summary')).toHaveText('Deletes a pet'); + + await test.step('Verify request', async () => { + + const request = page.getByRole('region', { name: 'Request' }); + await expect(request.getByRole('tab', { name: 'Body' })).toBeDisabled(); + await expect(request.getByRole('tab', { name: 'Parameters' })).not.toBeDisabled(); + await expect(request.getByRole('tab', { name: 'Security' })).not.toBeDisabled(); + await expect(request.getByRole('tabpanel', { name: 'Parameters' })).toBeVisible(); + + const parameters = request.getByRole('table', { name: 'Parameters' }); + const rows = parameters.locator('tbody tr'); + await expect(rows).toHaveCount(2) + + await test.step('Verify parameters', async () => { + + await expect(await getCellByColumnName(parameters, 'Name', rows.nth(0))).toHaveText('petId'); + await expect(await getCellByColumnName(parameters, 'Location', rows.nth(0))).toHaveText('path'); + await expect(await getCellByColumnName(parameters, 'Type', rows.nth(0))).toHaveText('integer'); + await expect(await getCellByColumnName(parameters, 'Required', rows.nth(0))).toHaveText('true'); + await expect(await getCellByColumnName(parameters, 'Description', rows.nth(0))).toHaveText('Pet id to delete'); + + await rows.nth(0).click(); + const dialog = page.getByRole('dialog', { name: 'Parameter Details' }); + await expect(dialog.getByLabel('Name')).toHaveText('petId'); + await expect(dialog.getByLabel('Location')).toHaveText('path'); + await expect(dialog.getByLabel('Required')).toHaveText('true'); + await expect(dialog.getByLabel('Style')).toHaveText('simple'); + await expect(dialog.getByLabel('Explode')).toHaveText('false'); + await expect(dialog.getByLabel('Allow Reserved')).toHaveText('false'); + await expect(dialog.getByLabel('Description')).toHaveText('Pet id to delete'); + await dialog.getByRole('button', { name: 'Close' }).click(); + + }); + + await request.getByRole('tab', { name: 'Security' }).click(); + const security = request.getByRole('tabpanel', { name: 'Security' }); + await expect(security).toBeVisible(); + await expect(security.getByLabel('Name')).toHaveText('petstore_auth'); + await expect(security.getByLabel('Type')).toHaveText('oauth2'); + await expect(security.getByLabel('Scopes')).toHaveText('write:pets, read:pets'); + const flows = security.getByRole('table', { name: 'Flows' }); + await expect(flows.locator('tbody tr')).toHaveCount(1); + await expect(await getCellByColumnName(flows, 'Type')).toHaveText('implicit'); + await expect(await getCellByColumnName(flows, 'Scopes')).toHaveText('read:petswrite:pets'); + await expect(await getCellByColumnName(flows, 'Authorization URL')).toHaveText('http://petstore.swagger.io/oauth/dialog'); + + }); + + await test.step('Verify response', async () => { + + const response = page.getByRole('region', { name: 'Response' }); + await expect(response.getByRole('tab', { name: '400 Bad Request' })).toBeVisible(); + await expect(response.getByRole('tab', { name: '404 Not Found' })).toBeVisible(); + + const tab400 = response.getByRole('tabpanel', { name: '400 Bad Request' }); + await expect(tab400).toBeVisible(); + await expect(tab400.getByLabel('Description')).toHaveText('Invalid ID supplied'); + + await response.getByRole('tab', { name: '404 Not Found' }).click(); + const tab404 = response.getByRole('tabpanel', { name: '404 Not Found' }); + await expect(tab404).toBeVisible(); + await expect(tab404.getByLabel('Description')).toHaveText('Pet not found'); + + }); + }); +}); \ No newline at end of file diff --git a/webui/e2e/header.dashboard.spec.ts b/webui/e2e/header.dashboard.spec.ts new file mode 100644 index 000000000..39aaf7185 --- /dev/null +++ b/webui/e2e/header.dashboard.spec.ts @@ -0,0 +1,28 @@ +import { test, expect } from './models/fixture-dashboard' + +test('header in dashboard', async ({ dashboard }) => { + await dashboard.open() + + await test.step("navigation links", async () => { + const links = dashboard.header.getNavLinks() + await expect(links.nth(0)).toHaveText('Dashboard') + if (process.env.CI) { + await expect(links.nth(1)).toHaveText('Guides') + await expect(links.nth(2)).toHaveText('Configuration') + await expect(links.nth(3)).toHaveText('JavaScript API') + await expect(links.nth(4)).toHaveText('Resources') + await expect(links.nth(5)).toHaveText('References') + } else { + await expect(links.nth(1)).toHaveText('Dashboard') + await expect(links.nth(2)).toHaveText('Guides') + await expect(links.nth(3)).toHaveText('Configuration') + await expect(links.nth(4)).toHaveText('JavaScript API') + await expect(links.nth(5)).toHaveText('Resources') + await expect(links.nth(6)).toHaveText('References') + } + }) + + await test.step('version number', async() => { + await expect(dashboard.header.version).toHaveText('v0.11.0') + }) +}) \ No newline at end of file diff --git a/webui/e2e/header.spec.ts b/webui/e2e/header.website.spec.ts similarity index 90% rename from webui/e2e/header.spec.ts rename to webui/e2e/header.website.spec.ts index dc4161523..5c47b8107 100644 --- a/webui/e2e/header.spec.ts +++ b/webui/e2e/header.website.spec.ts @@ -14,6 +14,6 @@ test('header in dashboard', async ({ dashboard }) => { }) await test.step('version number', async() => { - await expect(dashboard.header.version).toHaveText('v0.11.0') + await expect(dashboard.header.version).not.toBeVisible(); }) }) \ No newline at end of file diff --git a/webui/e2e/helpers/table.ts b/webui/e2e/helpers/table.ts new file mode 100644 index 000000000..31bc11359 --- /dev/null +++ b/webui/e2e/helpers/table.ts @@ -0,0 +1,16 @@ +import { Locator } from "@playwright/test"; + +export async function getCellByColumnName(table: Locator, columnName: string, row?: Locator | undefined): Promise { + const headerIndex = await table.getByRole('columnheader', { name: columnName, exact: true}) + .evaluate(header => { + const headers = Array.from(header.closest('tr').querySelectorAll('th')); + // Return 1-based index for nth-child CSS selector + return headers.indexOf(header as HTMLTableCellElement) + 1; + }); + + if (!row) { + row = table.locator('tbody tr') + } + + return row.locator(`td:nth-child(${headerIndex})`); +} \ No newline at end of file diff --git a/webui/e2e/models/http.ts b/webui/e2e/models/http.ts index 1ae9077c1..cdd5f413b 100644 --- a/webui/e2e/models/http.ts +++ b/webui/e2e/models/http.ts @@ -71,13 +71,13 @@ export class HttpOperationModel { constructor(readonly element: Locator) { this.operation = element.getByTestId('operation') this.path = element.getByTestId('path') - this.operationId = element.getByTestId('operationid') - this.service = element.getByTestId('service').getByRole('link') + this.operationId = element.getByLabel('Operation ID') + this.service = element.getByLabel('Service', { exact: true }).getByRole('link') this.type = element.getByTestId('type') - this.summary = element.getByTestId('summary') + this.summary = element.getByLabel('Summary') this.description = element.getByTestId('description') - this.request = new HttpOperationRequestModel(element.getByTestId('http-request')) - this.response = new HttpOperationResponseModel(element.getByTestId('http-response')) + this.request = new HttpOperationRequestModel(element.getByLabel('Request')) + this.response = new HttpOperationResponseModel(element.getByLabel('Response')) } } diff --git a/webui/index.html b/webui/index.html index a510d0d14..ae9d47a46 100644 --- a/webui/index.html +++ b/webui/index.html @@ -16,6 +16,15 @@ +
diff --git a/webui/package-lock.json b/webui/package-lock.json index a18f2c95c..3791d0ab3 100644 --- a/webui/package-lock.json +++ b/webui/package-lock.json @@ -11,17 +11,25 @@ "@popperjs/core": "^2.11.6", "@ssthouse/vue3-tree-chart": "^0.3.0", "@types/bootstrap": "^5.2.10", + "@types/mokapi": "^0.29.1", + "@types/nodemailer": "^7.0.4", "@types/whatwg-mimetype": "^3.0.2", "ace-builds": "^1.43.5", "bootstrap": "^5.3.8", "bootstrap-icons": "^1.13.1", + "cors": "^2.8.5", "dayjs": "^1.11.19", "del-cli": "^7.0.0", + "express": "^5.2.1", "fuse.js": "^7.1.0", "http-status-codes": "^2.3.0", "js-yaml": "^4.1.1", + "kafkajs": "^2.2.4", + "ldapts": "^8.1.2", + "mime-types": "^3.0.2", "ncp": "^2.0.0", - "vue": "^3.5.25", + "nodemailer": "^7.0.12", + "vue": "^3.5.26", "vue-router": "^4.6.4", "vue3-ace-editor": "^2.2.4", "vue3-highlightjs": "^1.0.5", @@ -33,18 +41,18 @@ "@playwright/test": "^1.57.0", "@rushstack/eslint-patch": "^1.15.0", "@types/js-yaml": "^4.0.9", - "@types/node": "^25.0.1", - "@vitejs/plugin-vue": "^6.0.2", + "@types/node": "^25.0.3", + "@vitejs/plugin-vue": "^6.0.3", "@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-typescript": "^14.6.0", "@vue/tsconfig": "^0.8.1", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "eslint-plugin-vue": "^10.6.2", "npm-run-all": "^4.1.5", "prettier": "^3.7.4", "typescript": "~5.9.3", - "vite": "^7.2.7", - "vue-tsc": "^3.1.8", + "vite": "^7.3.0", + "vue-tsc": "^3.2.1", "xml2js": "^0.6.2" } }, @@ -57,328 +65,814 @@ "node": ">=0.10.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "license": "MIT", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", - "license": "MIT", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", - "license": "MIT", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=16.0.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=14.0.0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=14.0.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=14.0.0" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "node_modules/@aws-sdk/client-sesv2": { + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.958.0.tgz", + "integrity": "sha512-3x3n8IIxIMAkdpt9wy9zS7MO2lqTcJwQTdHMn6BlD7YUohb+r5Q4KCOEQ2uHWd4WIJv2tlbXnfypHaXReO/WXA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/credential-provider-node": "3.958.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/signature-v4-multi-region": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "node_modules/@aws-sdk/client-sso": { + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.958.0.tgz", + "integrity": "sha512-6qNCIeaMzKzfqasy2nNRuYnMuaMebCcCPP4J2CVGkA8QYMbIVKPlkn9bpB20Vxe6H/r3jtCCLQaOJjVTx/6dXg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@aws-sdk/core": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.957.0.tgz", + "integrity": "sha512-DrZgDnF1lQZv75a52nFWs6MExihJF2GZB6ETZRqr6jMwhrk2kbJPUtvgbifwcL7AYmVqHQDJBrR/MqkwwFCpiw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@aws-sdk/xml-builder": "3.957.0", + "@smithy/core": "^3.20.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.957.0.tgz", + "integrity": "sha512-475mkhGaWCr+Z52fOOVb/q2VHuNvqEDixlYIkeaO6xJ6t9qR0wpLt4hOQaR6zR1wfZV0SlE7d8RErdYq/PByog==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.957.0.tgz", + "integrity": "sha512-8dS55QHRxXgJlHkEYaCGZIhieCs9NU1HU1BcqQ4RfUdSsfRdxxktqUKgCnBnOOn0oD3PPA8cQOCAVgIyRb3Rfw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-stream": "^4.5.8", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.958.0.tgz", + "integrity": "sha512-u7twvZa1/6GWmPBZs6DbjlegCoNzNjBsMS/6fvh5quByYrcJr/uLd8YEr7S3UIq4kR/gSnHqcae7y2nL2bqZdg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/credential-provider-env": "3.957.0", + "@aws-sdk/credential-provider-http": "3.957.0", + "@aws-sdk/credential-provider-login": "3.958.0", + "@aws-sdk/credential-provider-process": "3.957.0", + "@aws-sdk/credential-provider-sso": "3.958.0", + "@aws-sdk/credential-provider-web-identity": "3.958.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.958.0.tgz", + "integrity": "sha512-sDwtDnBSszUIbzbOORGh5gmXGl9aK25+BHb4gb1aVlqB+nNL2+IUEJA62+CE55lXSH8qXF90paivjK8tOHTwPA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.958.0.tgz", + "integrity": "sha512-vdoZbNG2dt66I7EpN3fKCzi6fp9xjIiwEA/vVVgqO4wXCGw8rKPIdDUus4e13VvTr330uQs2W0UNg/7AgtquEQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.957.0", + "@aws-sdk/credential-provider-http": "3.957.0", + "@aws-sdk/credential-provider-ini": "3.958.0", + "@aws-sdk/credential-provider-process": "3.957.0", + "@aws-sdk/credential-provider-sso": "3.958.0", + "@aws-sdk/credential-provider-web-identity": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.957.0.tgz", + "integrity": "sha512-/KIz9kadwbeLy6SKvT79W81Y+hb/8LMDyeloA2zhouE28hmne+hLn0wNCQXAAupFFlYOAtZR2NTBs7HBAReJlg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.958.0.tgz", + "integrity": "sha512-CBYHJ5ufp8HC4q+o7IJejCUctJXWaksgpmoFpXerbjAso7/Fg7LLUu9inXVOxlHKLlvYekDXjIUBXDJS2WYdgg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.958.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/token-providers": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.958.0.tgz", + "integrity": "sha512-dgnvwjMq5Y66WozzUzxNkCFap+umHUtqMMKlr8z/vl9NYMLem/WUbWNpFFOVFWquXikc+ewtpBMR4KEDXfZ+KA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.957.0.tgz", + "integrity": "sha512-BBgKawVyfQZglEkNTuBBdC3azlyqNXsvvN4jPkWAiNYcY0x1BasaJFl+7u/HisfULstryweJq/dAvIZIxzlZaA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.957.0.tgz", + "integrity": "sha512-w1qfKrSKHf9b5a8O76yQ1t69u6NWuBjr5kBX+jRWFx/5mu6RLpqERXRpVJxfosbep7k3B+DSB5tZMZ82GKcJtQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.957.0.tgz", + "integrity": "sha512-D2H/WoxhAZNYX+IjkKTdOhOkWQaK0jjJrDBj56hKjU5c9ltQiaX/1PqJ4dfjHntEshJfu0w+E6XJ+/6A6ILBBA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.957.0.tgz", + "integrity": "sha512-5B2qY2nR2LYpxoQP0xUum5A1UNvH2JQpLHDH1nWFNF/XetV7ipFHksMxPNhtJJ6ARaWhQIDXfOUj0jcnkJxXUg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-arn-parser": "3.957.0", + "@smithy/core": "^3.20.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-stream": "^4.5.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.957.0.tgz", + "integrity": "sha512-50vcHu96XakQnIvlKJ1UoltrFODjsq2KvtTgHiPFteUS884lQnK5VC/8xd1Msz/1ONpLMzdCVproCQqhDTtMPQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@smithy/core": "^3.20.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.958.0.tgz", + "integrity": "sha512-/KuCcS8b5TpQXkYOrPLYytrgxBhv81+5pChkOlhegbeHttjM69pyUpQVJqyfDM/A7wPLnDrzCAnk4zaAOkY0Nw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.957.0.tgz", + "integrity": "sha512-V8iY3blh8l2iaOqXWW88HbkY5jDoWjH56jonprG/cpyqqCnprvpMUZWPWYJoI8rHRf2bqzZeql1slxG6EnKI7A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.957.0.tgz", + "integrity": "sha512-t6UfP1xMUigMMzHcb7vaZcjv7dA2DQkk9C/OAP1dKyrE0vb4lFGDaTApi17GN6Km9zFxJthEMUbBc7DL0hq1Bg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.958.0.tgz", + "integrity": "sha512-UCj7lQXODduD1myNJQkV+LYcGYJ9iiMggR8ow8Hva1g3A/Na5imNXzz6O67k7DAee0TYpy+gkNw+SizC6min8Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.957.0.tgz", + "integrity": "sha512-wzWC2Nrt859ABk6UCAVY/WYEbAd7FjkdrQL6m24+tfmWYDNRByTJ9uOgU/kw9zqLCAwb//CPvrJdhqjTznWXAg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.957.0.tgz", + "integrity": "sha512-Aj6m+AyrhWyg8YQ4LDPg2/gIfGHCEcoQdBt5DeSFogN5k9mmJPOJ+IAmNSWmWRjpOxEy6eY813RNDI6qS97M0g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.957.0.tgz", + "integrity": "sha512-xwF9K24mZSxcxKS3UKQFeX/dPYkEps9wF1b+MGON7EvnbcucrJGyQyK1v1xFPn1aqXkBTFi+SZaMRx5E5YCVFw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-endpoints": "^3.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.957.0.tgz", + "integrity": "sha512-nhmgKHnNV9K+i9daumaIz8JTLsIIML9PE/HUks5liyrjUzenjW/aHoc7WJ9/Td/gPZtayxFnXQSJRb/fDlBuJw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.957.0.tgz", + "integrity": "sha512-exueuwxef0lUJRnGaVkNSC674eAiWU07ORhxBnevFFZEKisln+09Qrtw823iyv5I1N8T+wKfh95xvtWQrNKNQw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.957.0", + "@smithy/types": "^4.11.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.957.0.tgz", + "integrity": "sha512-ycbYCwqXk4gJGp0Oxkzf2KBeeGBdTxz559D41NJP8FlzSej1Gh7Rk40Zo6AyTfsNWkrl/kVi1t937OIzC5t+9Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.957.0.tgz", + "integrity": "sha512-Ai5iiQqS8kJ5PjzMhWcLKN0G2yasAkvpnPlq2EnqlIMdB48HsizElt62qcktdxp4neRMyGkFq4NzgmDbXnhRiA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.2.tgz", + "integrity": "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", "cpu": [ - "riscv64" + "ppc64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "aix" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "cpu": [ - "s390x" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "android" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", "cpu": [ "x64" ], @@ -386,16 +880,16 @@ "license": "MIT", "optional": true, "os": [ - "linux" + "android" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", "cpu": [ "arm64" ], @@ -403,16 +897,16 @@ "license": "MIT", "optional": true, "os": [ - "netbsd" + "darwin" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", "cpu": [ "x64" ], @@ -420,16 +914,16 @@ "license": "MIT", "optional": true, "os": [ - "netbsd" + "darwin" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", "cpu": [ "arm64" ], @@ -437,16 +931,16 @@ "license": "MIT", "optional": true, "os": [ - "openbsd" + "freebsd" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", "cpu": [ "x64" ], @@ -454,33 +948,33 @@ "license": "MIT", "optional": true, "os": [ - "openbsd" + "freebsd" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", "cpu": [ - "x64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "sunos" + "linux" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", "cpu": [ "arm64" ], @@ -488,16 +982,16 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "linux" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", "cpu": [ "ia32" ], @@ -505,33 +999,271 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "linux" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", "cpu": [ - "x64" + "loong64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "linux" ], "engines": { "node": ">=18" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { @@ -623,9 +1355,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", - "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "dev": true, "license": "MIT", "engines": { @@ -795,15 +1527,16 @@ "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.50.tgz", - "integrity": "sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==", + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", "dev": true, "license": "MIT" }, @@ -1045,65 +1778,645 @@ "linux" ] }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.15.0.tgz", + "integrity": "sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.7.tgz", + "integrity": "sha512-rzMY6CaKx2qxrbYbqjXWS0plqEy7LOdKHS0bg4ixJ6aoGDPNUcLWk/FRNuCILh7GKLG9TFUXYYeQQldMBBwuyw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.5.tgz", + "integrity": "sha512-HAGoUAFYsUkoSckuKbCPayECeMim8pOu+yLy1zOxt1sifzEbrsRpYa+mKcMdiHKMeiqOibyPG0sFJnmaV/OGEg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.20.0.tgz", + "integrity": "sha512-WsSHCPq/neD5G/MkK4csLI5Y5Pkd9c1NMfpYEKeghSGaD4Ja1qLIohRQf2D5c1Uy5aXp76DeKHkzWZ9KAlHroQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.8", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-stream": "^4.5.8", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.7.tgz", + "integrity": "sha512-CmduWdCiILCRNbQWFR0OcZlUPVtyE49Sr8yYL0rZQ4D/wKxiNzBNS/YHemvnbkIWj623fplgkexUd/c9CAKdoA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.8.tgz", + "integrity": "sha512-h/Fi+o7mti4n8wx1SR6UHWLaakwHRx29sizvp8OOm7iqwKGFneT06GCSFhml6Bha5BT6ot5pj3CYZnCHhGC2Rg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.7", + "@smithy/querystring-builder": "^4.2.7", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.7.tgz", + "integrity": "sha512-PU/JWLTBCV1c8FtB8tEFnY4eV1tSfBc7bDBADHfn1K+uRbPgSJ9jnJp0hyjiFN2PMdPzxsf1Fdu0eo9fJ760Xw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.7.tgz", + "integrity": "sha512-ncvgCr9a15nPlkhIUx3CU4d7E7WEuVJOV7fS7nnK2hLtPK9tYRBkMHQbhXU1VvvKeBm/O0x26OEoBq+ngFpOEQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.7.tgz", + "integrity": "sha512-GszfBfCcvt7kIbJ41LuNa5f0wvQCHhnGx/aDaZJCCT05Ld6x6U2s0xsc/0mBFONBZjQJp2U/0uSJ178OXOwbhg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.1.tgz", + "integrity": "sha512-gpLspUAoe6f1M6H0u4cVuFzxZBrsGZmjx2O9SigurTx4PbntYa4AJ+o0G0oGm1L2oSX6oBhcGHwrfJHup2JnJg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.20.0", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-middleware": "^4.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.17", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.17.tgz", + "integrity": "sha512-MqbXK6Y9uq17h+4r0ogu/sBT6V/rdV+5NvYL7ZV444BKfQygYe8wAhDrVXagVebN6w2RE0Fm245l69mOsPGZzg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/service-error-classification": "^4.2.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.8.tgz", + "integrity": "sha512-8rDGYen5m5+NV9eHv9ry0sqm2gI6W7mc1VSFMtn6Igo25S507/HaOX9LTHAS2/J32VXD0xSzrY0H5FJtOMS4/w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.7.tgz", + "integrity": "sha512-bsOT0rJ+HHlZd9crHoS37mt8qRRN/h9jRve1SXUhVbkRzu0QaNYZp1i1jha4n098tsvROjcwfLlfvcFuJSXEsw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.7.tgz", + "integrity": "sha512-7r58wq8sdOcrwWe+klL9y3bc4GW1gnlfnFOuL7CXa7UzfhzhxKuzNdtqgzmTV+53lEp9NXh5hY/S4UgjLOzPfw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.7.tgz", + "integrity": "sha512-NELpdmBOO6EpZtWgQiHjoShs1kmweaiNuETUpuup+cmm/xJYjT4eUjfhrXRP4jCOaAsS3c3yPsP3B+K+/fyPCQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/querystring-builder": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.7.tgz", + "integrity": "sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.7.tgz", + "integrity": "sha512-1r07pb994I20dD/c2seaZhoCuNYm0rWrvBxhCQ70brNh11M5Ml2ew6qJVo0lclB3jMIXirD4s2XRXRe7QEi0xA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.7.tgz", + "integrity": "sha512-eKONSywHZxK4tBxe2lXEysh8wbBdvDWiA+RIuaxZSgCMmA0zMgoDpGLJhnyj+c0leOQprVnXOmcB4m+W9Rw7sg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.7.tgz", + "integrity": "sha512-3X5ZvzUHmlSTHAXFlswrS6EGt8fMSIxX/c3Rm1Pni3+wYWB6cjGocmRIoqcQF9nU5OgGmL0u7l9m44tSUpfj9w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.7.tgz", + "integrity": "sha512-YB7oCbukqEb2Dlh3340/8g8vNGbs/QsNNRms+gv3N2AtZz9/1vSBx6/6tpwQpZMEJFs7Uq8h4mmOn48ZZ72MkA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.2.tgz", + "integrity": "sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.7.tgz", + "integrity": "sha512-9oNUlqBlFZFOSdxgImA6X5GFuzE7V2H7VG/7E70cdLhidFbdtvxxt81EHgykGK5vq5D3FafH//X+Oy31j3CKOg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.2.tgz", + "integrity": "sha512-D5z79xQWpgrGpAHb054Fn2CCTQZpog7JELbVQ6XAvXs5MNKWf28U9gzSBlJkOyMl9LA1TZEjRtwvGXfP0Sl90g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.20.0", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-stream": "^4.5.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.11.0.tgz", + "integrity": "sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.7.tgz", + "integrity": "sha512-/RLtVsRV4uY3qPWhBDsjwahAtt3x2IsMGnP5W1b2VZIe+qgCqkLxI1UOHDZp1Q1QSOrdOR32MF3Ph2JfWT1VHg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.16", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.16.tgz", + "integrity": "sha512-/eiSP3mzY3TsvUOYMeL4EqUX6fgUOj2eUOU4rMMgVbq67TiRLyxT7Xsjxq0bW3OwuzK009qOwF0L2OgJqperAQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.19", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.19.tgz", + "integrity": "sha512-3a4+4mhf6VycEJyHIQLypRbiwG6aJvbQAeRAVXydMmfweEPnLLabRbdyo/Pjw8Rew9vjsh5WCdhmDaHkQnhhhA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.5", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.7.tgz", + "integrity": "sha512-s4ILhyAvVqhMDYREeTS68R43B1V5aenV5q/V1QpRQJkCXib5BPRo4s7uNdzGtIKxaPHCfU/8YkvPAEvTpxgspg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/util-middleware": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.7.tgz", + "integrity": "sha512-i1IkpbOae6NvIKsEeLLM9/2q4X+M90KV3oCFgWQI4q0Qz+yUZvsr+gZPdAEAtFhWQhAHpTsJO8DRJPuwVyln+w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@smithy/util-retry": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.7.tgz", + "integrity": "sha512-SvDdsQyF5CIASa4EYVT02LukPHVzAgUA4kMAuZ97QJc2BpAqZfA4PINB8/KOoCXEw9tsuv/jQjMeaHFvxdLNGg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@rushstack/eslint-patch": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.15.0.tgz", - "integrity": "sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==", - "dev": true, - "license": "MIT" + "node_modules/@smithy/util-stream": { + "version": "4.5.8", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.8.tgz", + "integrity": "sha512-ZnnBhTapjM0YPGUSmOs0Mcg/Gg87k503qG4zU2v/+Js2Gu+daKOJMeqcQns8ajepY8tgzzfYxl6kQyZKml6O2w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@sindresorhus/merge-streams": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", - "license": "MIT", + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@ssthouse/tree-chart-core": { @@ -1155,8 +2468,7 @@ "node_modules/@types/linkify-it": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", - "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", - "peer": true + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==" }, "node_modules/@types/markdown-it": { "version": "12.2.3", @@ -1171,19 +2483,33 @@ "node_modules/@types/mdurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", - "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", - "peer": true + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==" + }, + "node_modules/@types/mokapi": { + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/@types/mokapi/-/mokapi-0.29.1.tgz", + "integrity": "sha512-Ozcf/eO6QDc3VnEKEDRa2S9POT3eis97kYZUqKSsnVN9IYyb/j3jbmDbWsjzeU/pYegwcNfSqGwFmUc+/1n0qg==", + "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.1.tgz", - "integrity": "sha512-czWPzKIAXucn9PtsttxmumiQ9N0ok9FrBwgRWrwmVLlp86BrMExzvXRLFYRJ+Ex3g6yqj+KuaxfX1JTgV2lpfg==", - "dev": true, + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", + "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", "license": "MIT", "dependencies": { "undici-types": "~7.16.0" } }, + "node_modules/@types/nodemailer": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.4.tgz", + "integrity": "sha512-ee8fxWqOchH+Hv6MDDNNy028kwvVnLplrStm4Zf/3uHWw5zzo8FoYYeffpJtGs2wWysEumMH0ZIdMGMY1eMAow==", + "license": "MIT", + "dependencies": { + "@aws-sdk/client-sesv2": "^3.839.0", + "@types/node": "*" + } + }, "node_modules/@types/whatwg-mimetype": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", @@ -1253,68 +2579,68 @@ } }, "node_modules/@vitejs/plugin-vue": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.2.tgz", - "integrity": "sha512-iHmwV3QcVGGvSC1BG5bZ4z6iwa1SOpAPWmnjOErd4Ske+lZua5K9TtAVdx0gMBClJ28DViCbSmZitjWZsWO3LA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.3.tgz", + "integrity": "sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==", "dev": true, "license": "MIT", "dependencies": { - "@rolldown/pluginutils": "1.0.0-beta.50" + "@rolldown/pluginutils": "1.0.0-beta.53" }, "engines": { "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.2.25" } }, "node_modules/@volar/language-core": { - "version": "2.4.26", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.26.tgz", - "integrity": "sha512-hH0SMitMxnB43OZpyF1IFPS9bgb2I3bpCh76m2WEK7BE0A0EzpYsRp0CCH2xNKshr7kacU5TQBLYn4zj7CG60A==", + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.27.tgz", + "integrity": "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==", "dev": true, "license": "MIT", "dependencies": { - "@volar/source-map": "2.4.26" + "@volar/source-map": "2.4.27" } }, "node_modules/@volar/source-map": { - "version": "2.4.26", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.26.tgz", - "integrity": "sha512-JJw0Tt/kSFsIRmgTQF4JSt81AUSI1aEye5Zl65EeZ8H35JHnTvFGmpDOBn5iOxd48fyGE+ZvZBp5FcgAy/1Qhw==", + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.27.tgz", + "integrity": "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==", "dev": true, "license": "MIT" }, "node_modules/@volar/typescript": { - "version": "2.4.26", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.26.tgz", - "integrity": "sha512-N87ecLD48Sp6zV9zID/5yuS1+5foj0DfuYGdQ6KHj/IbKvyKv1zNX6VCmnKYwtmHadEO6mFc2EKISiu3RDPAvA==", + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.27.tgz", + "integrity": "sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==", "dev": true, "license": "MIT", "dependencies": { - "@volar/language-core": "2.4.26", + "@volar/language-core": "2.4.27", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "node_modules/@vue/compiler-core": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.25.tgz", - "integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.26.tgz", + "integrity": "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==", "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", - "@vue/shared": "3.5.25", - "entities": "^4.5.0", + "@vue/shared": "3.5.26", + "entities": "^7.0.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-core/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.0.tgz", + "integrity": "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -1324,26 +2650,26 @@ } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.25.tgz", - "integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz", + "integrity": "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/compiler-core": "3.5.26", + "@vue/shared": "3.5.26" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.25.tgz", - "integrity": "sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz", + "integrity": "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==", "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", - "@vue/compiler-core": "3.5.25", - "@vue/compiler-dom": "3.5.25", - "@vue/compiler-ssr": "3.5.25", - "@vue/shared": "3.5.25", + "@vue/compiler-core": "3.5.26", + "@vue/compiler-dom": "3.5.26", + "@vue/compiler-ssr": "3.5.26", + "@vue/shared": "3.5.26", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", @@ -1351,13 +2677,13 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.25.tgz", - "integrity": "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz", + "integrity": "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/compiler-dom": "3.5.26", + "@vue/shared": "3.5.26" } }, "node_modules/@vue/devtools-api": { @@ -1618,6 +2944,7 @@ "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.38.0", "@typescript-eslint/types": "8.38.0", @@ -1706,27 +3033,19 @@ } }, "node_modules/@vue/language-core": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.1.8.tgz", - "integrity": "sha512-PfwAW7BLopqaJbneChNL6cUOTL3GL+0l8paYP5shhgY5toBNidWnMXWM+qDwL7MC9+zDtzCF2enT8r6VPu64iw==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.2.1.tgz", + "integrity": "sha512-g6oSenpnGMtpxHGAwKuu7HJJkNZpemK/zg3vZzZbJ6cnnXq1ssxuNrXSsAHYM3NvH8p4IkTw+NLmuxyeYz4r8A==", "dev": true, "license": "MIT", "dependencies": { - "@volar/language-core": "2.4.26", + "@volar/language-core": "2.4.27", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.0.0", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.2" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } } }, "node_modules/@vue/language-core/node_modules/picomatch": { @@ -1743,53 +3062,53 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.25.tgz", - "integrity": "sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.26.tgz", + "integrity": "sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.25" + "@vue/shared": "3.5.26" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.25.tgz", - "integrity": "sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.26.tgz", + "integrity": "sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/reactivity": "3.5.26", + "@vue/shared": "3.5.26" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.25.tgz", - "integrity": "sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.26.tgz", + "integrity": "sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.25", - "@vue/runtime-core": "3.5.25", - "@vue/shared": "3.5.25", - "csstype": "^3.1.3" + "@vue/reactivity": "3.5.26", + "@vue/runtime-core": "3.5.26", + "@vue/shared": "3.5.26", + "csstype": "^3.2.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.25.tgz", - "integrity": "sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.26.tgz", + "integrity": "sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/compiler-ssr": "3.5.26", + "@vue/shared": "3.5.26" }, "peerDependencies": { - "vue": "3.5.25" + "vue": "3.5.26" } }, "node_modules/@vue/shared": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.25.tgz", - "integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.26.tgz", + "integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==", "license": "MIT" }, "node_modules/@vue/tsconfig": { @@ -1811,17 +3130,32 @@ } } }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/ace-builds": { "version": "1.43.5", "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.43.5.tgz", "integrity": "sha512-iH5FLBKdB7SVn9GR37UgA/tpQS8OTWIxWAuq3Ofaw+Qbc69FfPXsXd9jeW7KRG2xKpKMqBDnu0tHBrCWY5QI7A==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1856,9 +3190,9 @@ } }, "node_modules/alien-signals": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.1.1.tgz", - "integrity": "sha512-ogkIWbVrLwKtHY6oOAXaYkAxP+cTH7V5FZ5+Tm4NZFd8VDZ6uNMDrfzqctTZ42eTMCSR3ne3otpcxmqSnFfPYA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.1.2.tgz", + "integrity": "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==", "dev": true, "license": "MIT" }, @@ -1900,6 +3234,46 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/body-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -1941,6 +3315,12 @@ ], "license": "MIT" }, + "node_modules/bowser": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", + "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1962,6 +3342,15 @@ "node": ">=8" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -1979,7 +3368,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -1993,7 +3381,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -2065,6 +3452,59 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2416,6 +3856,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -2507,10 +3948,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2597,11 +4037,19 @@ "robust-predicates": "^3.0.2" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -2612,6 +4060,21 @@ "node": ">= 0.4" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/entities": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", @@ -2680,7 +4143,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2690,7 +4152,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2700,7 +4161,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -2741,9 +4201,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2754,32 +4214,39 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" - } + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "4.0.0", @@ -2794,11 +4261,12 @@ } }, "node_modules/eslint": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", - "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2806,7 +4274,7 @@ "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.1", + "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -2859,6 +4327,7 @@ "integrity": "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "build/bin/cli.js" }, @@ -2903,6 +4372,7 @@ "integrity": "sha512-nA5yUs/B1KmKzvC42fyD0+l9Yd+LtEpVhWRbXuDj0e+ZURcTtyRbMDWUeJmTAh2wC6jC83raS63anNM2YT3NPw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", @@ -3048,6 +4518,58 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3102,6 +4624,24 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -3134,6 +4674,27 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3180,6 +4741,24 @@ "is-callable": "^1.1.3" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -3198,7 +4777,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3244,7 +4822,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -3269,7 +4846,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -3368,7 +4944,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3448,7 +5023,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3476,7 +5050,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3493,6 +5066,26 @@ "node": "*" } }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/http-status-codes": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", @@ -3546,6 +5139,12 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", @@ -3569,6 +5168,15 @@ "node": ">=12" } }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-array-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", @@ -3734,6 +5342,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -3867,6 +5481,15 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/kafkajs": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/kafkajs/-/kafkajs-2.2.4.tgz", + "integrity": "sha512-j/YeapB1vfPT2iOIUn/vxdyKEuhuY2PxMBvf5JWux6iSaukAccrMtXEY/Lb7OvavDhOWME589bpLrEdnVHjfjA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -3877,6 +5500,19 @@ "json-buffer": "3.0.1" } }, + "node_modules/ldapts": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/ldapts/-/ldapts-8.1.2.tgz", + "integrity": "sha512-QQAYM0fVzBcNzdo1VssKj9+v+BpjuyUqpgVjIUn1rWLdDj5cx60TtYoUWR5Ch9IMct1+jY92ES3G5fOoQaN34Q==", + "license": "MIT", + "dependencies": { + "strict-event-emitter-types": "2.0.0", + "whatwg-url": "15.1.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3952,6 +5588,7 @@ "version": "12.3.2", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "peer": true, "dependencies": { "argparse": "^2.0.1", "entities": "~2.1.0", @@ -4043,7 +5680,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4054,6 +5690,15 @@ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -4075,6 +5720,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4095,6 +5752,31 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4111,7 +5793,6 @@ "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": { @@ -4153,12 +5834,30 @@ "ncp": "bin/ncp" } }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node_modules/nodemailer": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.12.tgz", + "integrity": "sha512-H+rnK5bX2Pi/6ms3sN4/jRQvYSMltV6vqup/0SFOrxYYY/qoNvhXPlYq3e+Pm9RFJRwrMGbMIwi81M4dxpomhA==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/npm-run-all": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", @@ -4335,11 +6034,19 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4375,6 +6082,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -4460,6 +6188,15 @@ "node": ">=4" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", @@ -4492,6 +6229,16 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/path-type": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", @@ -4642,6 +6389,7 @@ "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -4665,16 +6413,43 @@ "node": ">=6.0.0" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4694,6 +6469,46 @@ } ] }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -4851,6 +6666,22 @@ "fsevents": "~2.3.2" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4918,6 +6749,57 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4954,7 +6836,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4974,7 +6855,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4991,7 +6871,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5010,7 +6889,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -5078,6 +6956,21 @@ "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", "dev": true }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/strict-event-emitter-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-event-emitter-types/-/strict-event-emitter-types-2.0.0.tgz", + "integrity": "sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA==", + "license": "ISC" + }, "node_modules/string.prototype.padend": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.4.tgz", @@ -5145,6 +7038,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5227,6 +7132,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -5245,6 +7151,27 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -5261,7 +7188,6 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, "license": "0BSD" }, "node_modules/type-check": { @@ -5276,6 +7202,20 @@ "node": ">= 0.8.0" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typed-array-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", @@ -5296,6 +7236,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5328,7 +7269,6 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, "license": "MIT" }, "node_modules/unicorn-magic": { @@ -5343,6 +7283,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -5370,14 +7319,24 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vite": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.7.tgz", - "integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", + "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "esbuild": "^0.25.0", + "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", @@ -5483,6 +7442,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -5498,16 +7458,17 @@ "license": "MIT" }, "node_modules/vue": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.25.tgz", - "integrity": "sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.26.tgz", + "integrity": "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==", "license": "MIT", + "peer": true, "dependencies": { - "@vue/compiler-dom": "3.5.25", - "@vue/compiler-sfc": "3.5.25", - "@vue/runtime-dom": "3.5.25", - "@vue/server-renderer": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/compiler-dom": "3.5.26", + "@vue/compiler-sfc": "3.5.26", + "@vue/runtime-dom": "3.5.26", + "@vue/server-renderer": "3.5.26", + "@vue/shared": "3.5.26" }, "peerDependencies": { "typescript": "*" @@ -5570,14 +7531,14 @@ } }, "node_modules/vue-tsc": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.1.8.tgz", - "integrity": "sha512-deKgwx6exIHeZwF601P1ktZKNF0bepaSN4jBU3AsbldPx9gylUc1JDxYppl82yxgkAgaz0Y0LCLOi+cXe9HMYA==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.2.1.tgz", + "integrity": "sha512-I23Rk8dkQfmcSbxDO0dmg9ioMLjKA1pjlU3Lz6Jfk2pMGu3Uryu9810XkcZH24IzPbhzPCnkKo2rEMRX0skSrw==", "dev": true, "license": "MIT", "dependencies": { - "@volar/typescript": "2.4.26", - "@vue/language-core": "3.1.8" + "@volar/typescript": "2.4.27", + "@vue/language-core": "3.2.1" }, "bin": { "vue-tsc": "bin/vue-tsc.js" @@ -5629,6 +7590,15 @@ "markdown-it-toc-done-right": "^4.2.0" } }, + "node_modules/webidl-conversions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", + "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, "node_modules/whatwg-mimetype": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", @@ -5637,6 +7607,19 @@ "node": ">=18" } }, + "node_modules/whatwg-url": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "license": "MIT", + "dependencies": { + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5689,6 +7672,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, "node_modules/xml-formatter": { "version": "3.6.7", "resolved": "https://registry.npmjs.org/xml-formatter/-/xml-formatter-3.6.7.tgz", diff --git a/webui/package.json b/webui/package.json index f442565a7..c7b9656c2 100644 --- a/webui/package.json +++ b/webui/package.json @@ -5,6 +5,7 @@ "scripts": { "dev": "vite", "build": "run-p type-check build-only", + "build-dashboard": "vite build --mode dashboard", "build-website": "vite build --mode website", "preview": "vite preview", "test:e2e": "playwright test", @@ -19,17 +20,25 @@ "@popperjs/core": "^2.11.6", "@ssthouse/vue3-tree-chart": "^0.3.0", "@types/bootstrap": "^5.2.10", + "@types/mokapi": "^0.29.1", + "@types/nodemailer": "^7.0.4", "@types/whatwg-mimetype": "^3.0.2", "ace-builds": "^1.43.5", "bootstrap": "^5.3.8", "bootstrap-icons": "^1.13.1", + "cors": "^2.8.5", "dayjs": "^1.11.19", "del-cli": "^7.0.0", + "express": "^5.2.1", "fuse.js": "^7.1.0", "http-status-codes": "^2.3.0", "js-yaml": "^4.1.1", + "kafkajs": "^2.2.4", + "ldapts": "^8.1.2", + "mime-types": "^3.0.2", "ncp": "^2.0.0", - "vue": "^3.5.25", + "nodemailer": "^7.0.12", + "vue": "^3.5.26", "vue-router": "^4.6.4", "vue3-ace-editor": "^2.2.4", "vue3-highlightjs": "^1.0.5", @@ -41,18 +50,18 @@ "@playwright/test": "^1.57.0", "@rushstack/eslint-patch": "^1.15.0", "@types/js-yaml": "^4.0.9", - "@types/node": "^25.0.1", - "@vitejs/plugin-vue": "^6.0.2", + "@types/node": "^25.0.3", + "@vitejs/plugin-vue": "^6.0.3", "@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-typescript": "^14.6.0", "@vue/tsconfig": "^0.8.1", - "eslint": "^9.39.1", + "eslint": "^9.39.2", "eslint-plugin-vue": "^10.6.2", "npm-run-all": "^4.1.5", "prettier": "^3.7.4", "typescript": "~5.9.3", - "vite": "^7.2.7", - "vue-tsc": "^3.1.8", + "vite": "^7.3.0", + "vue-tsc": "^3.2.1", "xml2js": "^0.6.2" } } diff --git a/webui/playwright.config.ts b/webui/playwright.config.ts index 4ce1d9407..cef9e554e 100644 --- a/webui/playwright.config.ts +++ b/webui/playwright.config.ts @@ -43,8 +43,48 @@ const config: PlaywrightTestConfig = { /* Configure projects for major browsers */ projects: [ + { + name: 'dev', + use: { + ...devices['Desktop Chrome'], + storageState: { + cookies: [], + origins: [ + { + origin: 'http://localhost:5173', + localStorage: [ + { + name: 'theme', value: 'dark' + } + ] + } + ] + }, + }, + testIgnore: ["/e2e/**/*.website.spec.ts"], + }, + { + name: 'dashboard', + use: { + ...devices['Desktop Chrome'], + storageState: { + cookies: [], + origins: [ + { + origin: 'http://localhost:5173', + localStorage: [ + { + name: 'theme', value: 'dark' + } + ] + } + ] + }, + }, + testIgnore: ["/e2e/**/*.website.spec.ts", "/e2e/dashboard-demo/**/*.spec.ts"], + }, { - name: 'chromium', + name: 'website', use: { ...devices['Desktop Chrome'], storageState: { @@ -60,7 +100,8 @@ const config: PlaywrightTestConfig = { } ] }, - } + }, + testIgnore: ["/e2e/**/*.dashboard.spec.ts", "/e2e/dashboard/**/*.spec.ts"], }, // { // name: 'firefox', @@ -114,7 +155,7 @@ const config: PlaywrightTestConfig = { * Use the preview server on CI for more realistic testing. Playwright will re-use the local server if there is already a dev-server running. */ - command: process.env.CI ? 'vite preview --port 5173' : 'vite dev', + command: process.env.CI ? 'vite preview --mode dashboard --port 5173' : 'vite dev', port: 5173, reuseExistingServer: !process.env.CI } diff --git a/webui/public/.htaccess b/webui/public/.htaccess index f951f7940..96a51e425 100644 --- a/webui/public/.htaccess +++ b/webui/public/.htaccess @@ -1,3 +1,6 @@ +# Disable automatic Apache slash redirects +DirectorySlash Off + Options -MultiViews @@ -40,6 +43,11 @@ # Rewrite /about/ → /about/index.html RewriteRule ^(.+)/$ $1/index.html [L] + # rewrite to /index.html ff the request is a directory but has no trailing slash + RewriteCond %{HTTP_USER_AGENT} googlebot|bingbot|Seobility|yandex|baiduspider|facebookexternalhit|twitterbot|rogerbot|linkedinbot|embedly|quora\ link\ preview|showyoubot|outbrain|pinterest\/0\.|pinterestbot|slackbot|vkShare|W3C_Validator|whatsapp|redditbot|applebot|flipboard|tumblr|bitlybot|skypeuripreview|nuzzel|discordbot|google\ page\ speed|qwantify|bitrix\ link\ preview|xing-contenttabreceiver|google-inspectiontool|chrome-lighthouse|telegrambot|amazonbot [NC] + RewriteCond %{REQUEST_FILENAME} -d + RewriteRule ^(.+[^/])$ $1/index.html [L] + # rewrite for bots RewriteCond %{HTTP_USER_AGENT} googlebot|bingbot|Seobility|yandex|baiduspider|facebookexternalhit|twitterbot|rogerbot|linkedinbot|embedly|quora\ link\ preview|showyoubot|outbrain|pinterest\/0\.|pinterestbot|slackbot|vkShare|W3C_Validator|whatsapp|redditbot|applebot|flipboard|tumblr|bitlybot|skypeuripreview|nuzzel|discordbot|google\ page\ speed|qwantify|bitrix\ link\ preview|xing-contenttabreceiver|google-inspectiontool|chrome-lighthouse|telegrambot|amazonbot [NC] RewriteCond %{REQUEST_URI} !\.(html|css|js|less|jpg|png|gif|svg|woff2|xml)$ diff --git a/webui/scripts/dashboard-demo/assets/bag.png b/webui/scripts/dashboard-demo/assets/bag.png new file mode 100644 index 000000000..821314d1c Binary files /dev/null and b/webui/scripts/dashboard-demo/assets/bag.png differ diff --git a/webui/scripts/dashboard-demo/assets/headphones.png b/webui/scripts/dashboard-demo/assets/headphones.png new file mode 100644 index 000000000..74a5823da Binary files /dev/null and b/webui/scripts/dashboard-demo/assets/headphones.png differ diff --git a/webui/scripts/dashboard-demo/assets/lead.png b/webui/scripts/dashboard-demo/assets/lead.png new file mode 100644 index 000000000..2cc4b7b1e Binary files /dev/null and b/webui/scripts/dashboard-demo/assets/lead.png differ diff --git a/webui/scripts/dashboard-demo/assets/watch.png b/webui/scripts/dashboard-demo/assets/watch.png new file mode 100644 index 000000000..9286f29d9 Binary files /dev/null and b/webui/scripts/dashboard-demo/assets/watch.png differ diff --git a/webui/scripts/dashboard-demo/ci.ts b/webui/scripts/dashboard-demo/ci.ts new file mode 100644 index 000000000..f481cc5fd --- /dev/null +++ b/webui/scripts/dashboard-demo/ci.ts @@ -0,0 +1,26 @@ +import { collectDashboard } from './collect-dashboard.ts'; +import { driveHttp } from './drive-http.ts'; +import { driveKafka, closeKafka } from './drive-kafka.ts'; +import { driveMail } from './drive-mail.ts'; +import { driveLdap } from './drive-ldap.ts'; + +async function main() { + try { + await driveHttp(); + await driveKafka(); + await driveMail(); + await driveLdap(); + + await collectDashboard(); + + } catch (err) { + console.error('❌ Failed to generate demo data') + console.error(err) + } finally { + await closeKafka(); + } + + process.exit(0) +} + +await main() \ No newline at end of file diff --git a/webui/scripts/dashboard-demo/collect-dashboard.ts b/webui/scripts/dashboard-demo/collect-dashboard.ts new file mode 100644 index 000000000..ea93dcc03 --- /dev/null +++ b/webui/scripts/dashboard-demo/collect-dashboard.ts @@ -0,0 +1,125 @@ +import fs from 'fs/promises' + +const baseUrl = 'http://localhost:8080'; +const output = '../../public/demo' + +export async function collectDashboard() { + if (! await directoryExists(output)) { + fs.mkdir(output) + } + + const endpoints = { + info: { path: '/api/info', loader: loadJson }, + services: { path: '/api/services' , loader: loadJson }, + metrics: { path: '/api/metrics?q=app', loader: loadJson }, + 'service_Swagger Petstore': { path: '/api/services/http/Swagger%20Petstore', loader: loadJson }, + 'service_Kafka Order Service API': { path: '/api/services/kafka/Kafka%20Order%20Service%20API', loader: loadJson }, + 'service_Mail Server': { path: '/api/services/mail/Mail%20Server', loader: loadJson }, + 'service_HR Employee Directory': { path: '/api/services/ldap/HR%20Employee%20Directory', loader: loadJson }, + events: { path: '/api/events', loader: fetchEvents }, + 'mailbox_alice.johnson@example.com': { path: '/api/services/mail/Mail%20Server/mailboxes/alice.johnson@example.com', loader: loadJson }, + 'mailbox_bob.miller@example.com': { path: '/api/services/mail/Mail%20Server/mailboxes/bob.miller@example.com', loader: loadJson }, + configs: { path: '/api/configs', loader: loadConfigs } + } + + const snapshot: Record = {} + + for (const [key, obj] of Object.entries(endpoints)) { + const url = baseUrl + obj.path; + await obj.loader(url, key, snapshot); + } + + await fs.writeFile(output + '/dashboard.json', JSON.stringify(snapshot, null, 2)); +} + +async function loadJson(url: string, key: string, snapshot: Record) { + snapshot[key] = await fetchJson(url) +} + +async function fetchJson(url: string): Promise { + try { + const res = await fetch(url); + if (!res.ok) { + let text = await res.text() + throw new Error(res.statusText + ': ' + text) + } + return await res.json() + } catch(e) { + console.error(`request ${url} failed: ${e}`) + } +} + +async function directoryExists(path: string) { + try { + const stats = await fs.stat(path); + return stats.isDirectory(); + } catch (err: any) { + return false + } +} + +async function fetchEvents(url: string, key: string, snapshot: Record) { + const events = await fetchJson(url); + snapshot[key] = events + + const mails = []; + for (const event of events) { + if (event.traits.namespace !== 'mail') { + continue; + } + + const url = `${baseUrl}/api/services/mail/messages/${event.data.messageId}` + const mail = await fetchJson(url); + mails.push(mail) + + if (mail.data.attachments) { + for (const attach of mail.data.attachments) { + const data = await fetchBinary(`${baseUrl}/api/services/mail/messages/${event.data.messageId}/attachments/${attach.name}`); + const filename = getFilenameWithRegex(attach.contentType); + await fs.writeFile(`${output}/${filename}`, data); + } + } + } + snapshot['mails'] = mails +} + +async function fetchBinary(url: string): Promise { + try { + const res = await fetch(url); + if (!res.ok) { + let text = await res.text() + throw new Error(res.statusText + ': ' + text) + } + const arrayBuffer = await res.arrayBuffer(); + return Buffer.from(arrayBuffer); + } catch(e) { + console.error(`request ${url} failed: ${e}`) + } +} + +function getFilenameWithRegex(contentType: string) { + const match = contentType.match(/name=([^;]+)/); + if (match && match[1]) { + return match[1]; + } + return null; +} + +async function loadConfigs(url: string, key: string, snapshot: Record) { + const configs = await fetchJson(url); + snapshot[key] = configs; + + for (const config of configs) { + const url = `${baseUrl}/api/configs/${config.id}`; + const cfg = await fetchJson(url); + snapshot['config_'+config.id] = cfg; + const filename = getFilenameFromUrl(cfg.url); + + const data = await fetchBinary(`${baseUrl}/api/configs/${config.id}/data`); + await fs.writeFile(`${output}/${filename}`, data); + } +} + +function getFilenameFromUrl(url: string): string { + return new URL(url).pathname.split('/').pop()!; +} \ No newline at end of file diff --git a/webui/scripts/dashboard-demo/demo-configs/asyncapi.yaml b/webui/scripts/dashboard-demo/demo-configs/asyncapi.yaml new file mode 100644 index 000000000..613679b19 --- /dev/null +++ b/webui/scripts/dashboard-demo/demo-configs/asyncapi.yaml @@ -0,0 +1,114 @@ +asyncapi: 3.0.0 +info: + title: Kafka Order Service API + version: 1.0.0 + description: An API to process customer orders and notify about order status updates using Kafka. + +servers: + development: + host: localhost:9092 + protocol: kafka + description: Local development Kafka broker. + +channels: + # The "order_events" channel is where order-related messages flow + order_events: + address: order-topic + description: The Kafka topic for order events. + messages: + OrderCreated: + $ref: '#/components/messages/OrderCreated' + +operations: + # Operation for publishing new orders + publishNewOrder: + action: send + channel: + $ref: '#/channels/order_events' + messages: + - $ref: '#/components/messages/OrderCreated' + bindings: + kafka: + clientId: + type: string + description: The producer client ID, must be prefixed with 'producer-'. + pattern: '^producer-[a-zA-Z0-9-]+$' + bindingVersion: 0.4.0 + + # Operation for subscribing to status updates (presumably from another service) + receiveOrderStatus: + action: receive + channel: + $ref: '#/channels/order_events' + messages: + - $ref: '#/components/messages/OrderStatusUpdated' + bindings: + kafka: + groupId: + type: string + description: The consumer group ID, must adhere to internal naming conventions. + pattern: '^order-status-group-[0-9]{3}$' + bindingVersion: 0.4.0 + +components: + messages: + OrderCreated: + name: OrderCreatedEvent + title: Order Created Event + summary: Notification that a new order has been created. + payload: + $ref: '#/components/schemas/OrderPayload' + examples: + - name: new-order-example + payload: + orderId: "12345" + productId: "PROD-ABC" + quantity: 2 + customerEmail: "customer@example.com" + status: "CREATED" + + OrderStatusUpdated: + name: OrderStatusUpdatedEvent + title: Order Status Updated Event + summary: Notification about an update in the order status. + payload: + $ref: '#/components/schemas/OrderPayload' + examples: + - name: status-update-example + payload: + orderId: "12345" + productId: "PROD-ABC" + quantity: 2 + customerEmail: "customer@example.com" + status: "SHIPPED" + + schemas: + OrderPayload: + type: object + properties: + orderId: + type: string + format: uuid + description: Unique ID of the order. + productId: + type: string + description: ID of the product ordered. + productName: + type: string + quantity: + type: integer + description: Number of items ordered. + customerEmail: + type: string + format: email + description: Email address of the customer. + status: + type: string + enum: [CREATED, PROCESSING, SHIPPED, DELIVERED, CANCELLED] + description: The current status of the order. + required: + - orderId + - productId + - quantity + - customerEmail + - status diff --git a/webui/scripts/dashboard-demo/demo-configs/employees.ldif b/webui/scripts/dashboard-demo/demo-configs/employees.ldif new file mode 100644 index 000000000..69037436c --- /dev/null +++ b/webui/scripts/dashboard-demo/demo-configs/employees.ldif @@ -0,0 +1,57 @@ +# Base DN entry for HR example +dn: dc=hr,dc=example,dc=com +objectClass: top +objectClass: dcObject +objectClass: organization +o: Example Corp HR +dc: hr + +# Organizational Unit: People +dn: ou=people,dc=hr,dc=example,dc=com +ou: people +objectClass: top +objectClass: organizationalUnit + +# User: Alice Johnson (In 'people' OU) +dn: uid=ajohnson,ou=people,dc=hr,dc=example,dc=com +cn: Alice Johnson +uid: ajohnson +userPassword: anothersecretpassword456 +givenName: Alice +sn: Johnson +title: Regional Manager +# NORMAL_ACCOUNT +userAccountControl: 512 +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson + +# User: Bob Miller (In 'people' OU) +dn: uid=bmiller,ou=people,dc=hr,dc=example,dc=com +cn: Bob Miller +uid: bmiller +userPassword: mysecretpassword123 +givenName: Bob +sn: Miller +title: Assistant Regional Manager +userAccountControl: 512 +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson + +# Organizational Unit: Departments (Groups) +dn: ou=departments,dc=hr,dc=example,dc=com +ou: departments +objectClass: top +objectClass: organizationalUnit + +# Group: Sales Department +dn: cn=Sales,ou=departments,dc=hr,dc=example,dc=com +cn: Sales +objectClass: top +objectClass: groupOfNames +member: uid=ajohnson,ou=people,dc=hr,dc=example,dc=com +member: uid=bmiller,ou=people,dc=hr,dc=example,dc=com +description: The Dunder Mifflin Sales Team. diff --git a/webui/scripts/dashboard-demo/demo-configs/kafka.ts b/webui/scripts/dashboard-demo/demo-configs/kafka.ts new file mode 100644 index 000000000..6883b3afe --- /dev/null +++ b/webui/scripts/dashboard-demo/demo-configs/kafka.ts @@ -0,0 +1,15 @@ +import { every } from 'mokapi'; +import kafka from 'mokapi/kafka'; + +export default function() { + let counter = 1; + every('5m', function() { + const result = kafka.produce({ + topic: 'order-topic', + messages: [ + { key: 'random-message-' + counter++ } + ], + }); + console.log(`offset=${result.messages[0].offset}, key=${result.messages[0].key}, value=${result.messages[0].value}`) + }, { times: 2, tags: { custom: 'random message generator' } }); +} \ No newline at end of file diff --git a/webui/scripts/dashboard-demo/demo-configs/ldap.yaml b/webui/scripts/dashboard-demo/demo-configs/ldap.yaml new file mode 100644 index 000000000..d61e9416e --- /dev/null +++ b/webui/scripts/dashboard-demo/demo-configs/ldap.yaml @@ -0,0 +1,9 @@ +ldap: 1.0.0 +info: + title: HR Employee Directory + description: LDAP server for internal employee contact information. + version: 1.0.0 +host: :8389 +files: + - ./schema.ldif + - ./employees.ldif \ No newline at end of file diff --git a/webui/scripts/dashboard-demo/demo-configs/mail.yaml b/webui/scripts/dashboard-demo/demo-configs/mail.yaml new file mode 100644 index 000000000..b9bc7f052 --- /dev/null +++ b/webui/scripts/dashboard-demo/demo-configs/mail.yaml @@ -0,0 +1,33 @@ +mail: '1.0' +info: + title: Mail Server + description: Configuration for the internal mail server. + version: 1.0.0 + contact: + name: Support Team + url: https://support.example.com + email: support@example.com + +servers: + smtp: + host: localhost:8025 + protocol: smtp + description: Primary outgoing mail server + imap: + host: localhost:8143 + protocol: imap + description: IMAP mail server for accessing mails + +mailboxes: + alice.johnson@example.com: + username: alice.johnson + password: anothersecretpassword456 + description: Configuration for Alice Johnson's. + + bob.miller@example.com: + username: bob.miller + password: mysecretpassword123 + description: Configuration for Bob Miller's. + + zzz@example.com: + description: TODO remove it \ No newline at end of file diff --git a/webui/scripts/dashboard-demo/demo-configs/petstore.ts b/webui/scripts/dashboard-demo/demo-configs/petstore.ts new file mode 100644 index 000000000..86ab80884 --- /dev/null +++ b/webui/scripts/dashboard-demo/demo-configs/petstore.ts @@ -0,0 +1,100 @@ +import { on } from 'mokapi'; +import { fake } from 'mokapi/faker' + +export default function() { + on('http', (request, response) => { + if (request.operationId === 'getUserByName') { + const username = request.path.username; + console.log(`[HTTP] Incoming request → GET /user/${username}`); + console.log(`[HTTP] Operation matched: getUserByName`); + console.log(`[Script] Looking up user: "${username}"`); + + if (users.has(username)) { + console.log(`[Script] ✓ User found`); + response.data = users.get(request.path.username); + } else { + console.log(`[Script] ✗ User not found → returning 404`); + response.statusCode = 404 + } + } + switch (request.key) { + case '/user/login': { + const username = request.query.username; + if (users.has(username)) { + const user = users.get(username); + console.log(`[Script] User ${username} found`) + if (user.password === request.query.password) { + response.data = 'ok' + } else { + console.log(`[Script] Password for ${username} is invalid`) + response.statusCode = 400; + } + } else { + console.log(`[Script] User ${username} not found`) + response.statusCode = 400; + } + break; + } + case '/store/order': { + orders.set(request.body.id, request.body); + response.data = request.body; + break; + } + case '/store/order/{orderId}': { + if (request.method === 'GET') { + const order = orders.get(request.path.orderId); + if (order) { + response.data = order; + } else { + response.statusCode = 404; + } + } else if (request.method === 'DELETE') { + const orderId = request.path.orderId; + if (orders.delete(orderId)) { + response.statusCode = 204; + } else { + response.statusCode = 404; + } + } + break; + } + case '/user/logout': { + // force a script error with undefined variable user + console.log(`logout current user ${user}`) + break; + } + } + }) +} + +const users = new Map([ + ['ajohnson', { + id: fake({ type: 'integer', format: 'int64' }), + username: 'ajohnson', + firstName: 'Alice', + lastName: 'Johnson', + email: 'alice.johnson@example.com', + password: 'anothersecretpassword456', + phone: fake({ type: 'string', pattern: '(?:(?:\\+|0{0,2})91(\\s*[\\- ]\\s*)?|[0 ]?)?[789]\\d{9}|(\\d[ -]?){10}\\d' }), + userStatus: 1 + }], + ['bmiller', { + id: fake({ type: 'integer', format: 'int64' }), + username: 'bmiller', + firstName: 'Bob', + lastName: 'Miller', + email: 'bob.miller@example.com', + password: 'mysecretpassword123', + phone: fake({ type: 'string', pattern: '(?:(?:\\+|0{0,2})91(\\s*[\\- ]\\s*)?|[0 ]?)?[789]\\d{9}|(\\d[ -]?){10}\\d' }), + userStatus: 1 + }] +]); + +const orders = new Map([ + [1, { + petId: 74959, + quantity: 1, + shipDate: '2011-01-08T16:59:15Z', + status: 'delivered' + }] +]) \ No newline at end of file diff --git a/webui/scripts/dashboard-demo/demo-configs/petstore.yaml b/webui/scripts/dashboard-demo/demo-configs/petstore.yaml new file mode 100644 index 000000000..d050dda51 --- /dev/null +++ b/webui/scripts/dashboard-demo/demo-configs/petstore.yaml @@ -0,0 +1,701 @@ +openapi: 3.1.0 +info: + description: 'This is a sample server Petstore server. You can find out more about + Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For + this sample, you can use the api key `special-key` to test the authorization filters.' + version: 1.0.0 + title: Swagger Petstore + termsOfService: http://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html +servers: + - url: http://petstore.swagger.io/v2 +externalDocs: + description: Find out more about Swagger + url: http://swagger.io +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: http://swagger.io + - name: store + description: Access to Petstore orders + - name: user + description: Operations about user + externalDocs: + description: Find out more about our store + url: http://swagger.io +paths: + '/pet': + post: + tags: + - pet + summary: Add a new pet to the store + description: '' + operationId: addPet + parameters: [] + responses: + '200': + description: pet successfully added + '405': + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + requestBody: + '$ref': '#/components/requestBodies/Pet' + put: + tags: + - pet + summary: Update an existing pet + description: '' + operationId: updatePet + parameters: [] + responses: + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - write:pets + - read:pets + requestBody: + '$ref': '#/components/requestBodies/Pet' + '/pet/findByStatus': + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + explode: true + schema: + type: array + items: + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + '$ref': '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + '$ref': '#/components/schemas/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - write:pets + - read:pets + '/pet/findByTags': + get: + tags: + - pet + summary: Finds Pets by tags + description: Muliple tags can be provided with comma separated strings. Use + tag1, tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + explode: true + schema: + type: array + items: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + '$ref': '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + '$ref': '#/components/schemas/Pet' + '400': + description: Invalid tag value + security: + - petstore_auth: + - write:pets + - read:pets + deprecated: true + '/pet/{petId}': + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + '$ref': '#/components/schemas/Pet' + application/json: + schema: + '$ref': '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + default: + description: successful response + security: + - api_key: [] + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + parameters: + - name: api_key + in: header + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - petstore_auth: + - write:pets + - read:pets + '/pet/{petId}/uploadImage': + post: + tags: + - pet + summary: uploads an image + description: '' + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + '$ref': '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - write:pets + - read:pets + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + '/store/inventory': + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + parameters: [] + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + '/store/order': + post: + tags: + - store + summary: Place an order for a pet + description: '' + operationId: placeOrder + parameters: [] + responses: + '200': + description: successful operation + content: + application/xml: + schema: + '$ref': '#/components/schemas/Order' + application/json: + schema: + '$ref': '#/components/schemas/Order' + '400': + description: Invalid Order + requestBody: + content: + application/json: + schema: + '$ref': '#/components/schemas/Order' + description: order placed for purchasing the pet + required: true + '/store/order/{orderId}': + get: + tags: + - store + summary: Find purchase order by ID + description: For valid response try integer IDs with value >= 1 and <= 10. Other + values will generated exceptions + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of pet that needs to be fetched + required: true + schema: + type: integer + format: int64 + minimum: 1 + maximum: 10 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + '$ref': '#/components/schemas/Order' + application/json: + schema: + '$ref': '#/components/schemas/Order' + '400': + description: Invalid ID supplied + '404': + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: For valid response try integer IDs with positive integer value. + Negative or non-integer values will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: integer + format: int64 + minimum: 1 + responses: + '400': + description: Invalid ID supplied + '404': + description: Order not found + '/user': + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + parameters: [] + responses: + default: + description: successful operation + requestBody: + content: + application/json: + schema: + '$ref': '#/components/schemas/User' + description: Created user object + required: true + '/user/createWithArray': + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithArrayInput + parameters: [] + responses: + default: + description: successful operation + requestBody: + '$ref': '#/components/requestBodies/UserArray' + '/user/createWithList': + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithListInput + parameters: [] + responses: + default: + description: successful operation + requestBody: + '$ref': '#/components/requestBodies/UserArray' + '/user/login': + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: true + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: true + schema: + type: string + responses: + '200': + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + '400': + description: Invalid username/password supplied + '/user/logout': + get: + tags: + - user + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + parameters: [] + responses: + default: + description: successful operation + '/user/{username}': + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + parameters: + - name: username + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + '$ref': '#/components/schemas/User' + application/json: + schema: + '$ref': '#/components/schemas/User' + '400': + description: Invalid username supplied + '404': + description: User not found + put: + tags: + - user + summary: Updated user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be updated + required: true + schema: + type: string + responses: + '400': + description: Invalid user supplied + '404': + description: User not found + requestBody: + content: + application/json: + schema: + '$ref': '#/components/schemas/User' + description: Updated user object + required: true + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + Category: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Category + User: + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + xml: + name: User + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + Pet: + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + readOnly: true + category: + '$ref': '#/components/schemas/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + '$ref': '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + requestBodies: + Pet: + content: + application/json: + schema: + '$ref': '#/components/schemas/Pet' + application/xml: + schema: + '$ref': '#/components/schemas/Pet' + description: Pet object that needs to be added to the store + required: true + UserArray: + content: + application/json: + schema: + type: array + items: + '$ref': '#/components/schemas/User' + description: List of user object + required: true + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: http://petstore.swagger.io/oauth/dialog + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/webui/scripts/dashboard-demo/demo-configs/schema.ldif b/webui/scripts/dashboard-demo/demo-configs/schema.ldif new file mode 100644 index 000000000..f95942b46 --- /dev/null +++ b/webui/scripts/dashboard-demo/demo-configs/schema.ldif @@ -0,0 +1,16 @@ +dn: +subschemaSubentry: cn=schema,cn=config + +dn: cn=schema,cn=config +objectClass: top +objectClass: subschema +cn: schema +# Define attributes +attributeTypes: ( 2.5.4.3 NAME 'cn' DESC 'Common Name' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) +attributeTypes: ( 0.9.2342.19200300.100.1.1 NAME 'uid' DESC 'User ID' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.4.1.1466.115.121.1.49 NAME 'userPassword' DESC 'User Password' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 ) +attributeTypes: ( 1.3.6.1.4.1.1466.115.121.1.26 NAME 'mail' DESC 'Email Address' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) +# Define object classes using the attributes +objectClasses: ( 2.5.6.0 NAME 'top' SUP NO-USER-MODIFICATION ) +objectClasses: ( 2.5.6.1 NAME 'person' SUP top MUST ( cn ) ) +objectClasses: ( 2.5.6.42 NAME 'user' SUP person MAY ( userPassword $ uid ) ) \ No newline at end of file diff --git a/webui/scripts/dashboard-demo/demo-configs/z.petstore.fix.yaml b/webui/scripts/dashboard-demo/demo-configs/z.petstore.fix.yaml new file mode 100644 index 000000000..328acb9a4 --- /dev/null +++ b/webui/scripts/dashboard-demo/demo-configs/z.petstore.fix.yaml @@ -0,0 +1,23 @@ +# The official Petstore OpenAPI example contains a few inconsistencies. +# One of them is that the DELETE /store/order/{orderId} operation +# does not define a successful (2xx) response. +# +# Mokapi’s patching mechanism allows us to fix such issues without +# modifying the original specification. +# +# Learn more about patching: +# https://mokapi.io/docs/configuration/patching +openapi: 3.1.0 +info: + title: Swagger Petstore # title is the patching discriminator +paths: + /pet/findByStatus: + get: + parameters: + - name: status + explode: false # Use comma-separated values as described + /store/order/{orderId}: + delete: + responses: + '204': + description: Order deleted successfully \ No newline at end of file diff --git a/webui/scripts/dashboard-demo/drive-http.ts b/webui/scripts/dashboard-demo/drive-http.ts new file mode 100644 index 000000000..c7e6e3e0a --- /dev/null +++ b/webui/scripts/dashboard-demo/drive-http.ts @@ -0,0 +1,51 @@ +export async function driveHttp() { + await fetch('http://localhost/v2/pet/10', { headers: { Accept: 'application/json', api_key: 'demo' }}) + await fetch('http://localhost/v2/pet', { + method: 'POST', + body: JSON.stringify({ name: 'Milo', photoUrls: [] }), + headers: { + Authorization: 'demo', + 'Content-Type': 'application/json' + } + }); + + await fetch('http://localhost/v2/user/bmiller'); + + // invalid request without password + await fetch('http://localhost/v2/user/login?username=ajohnson'); + // valid + await fetch('http://localhost/v2/user/login?username=ajohnson&password=anothersecretpassword456'); + // invalid password + await fetch('http://localhost/v2/user/login?username=bmiller&password=12345'); + await fetch('http://localhost/v2/user/login?username=bmiller&password=mysecretpassword123'); + // request with script error + await fetch('http://localhost/v2/user/login?username=bmiller&password=mysecretpassword123'); + // script error + await fetch('http://localhost/v2/user/logout'); + + // should only return pets with the given status. + await fetch('http://localhost/v2/pet/findByStatus?status=available,pending', { + headers: { + Accept: 'application/xml', + Authorization: 'demo' + } + }); + + await fetch('http://localhost/v2/store/order/1') + await fetch('http://localhost/v2/store/order', { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + id: 2, + petId: 14921, + quantity: 2, + status: 'placed' + }) + }) + await fetch('http://localhost/v2/store/order/2', { + method: 'DELETE', + }) +} \ No newline at end of file diff --git a/webui/scripts/dashboard-demo/drive-kafka.ts b/webui/scripts/dashboard-demo/drive-kafka.ts new file mode 100644 index 000000000..19b4ca4c5 --- /dev/null +++ b/webui/scripts/dashboard-demo/drive-kafka.ts @@ -0,0 +1,44 @@ +import { Kafka } from 'kafkajs'; + +const kafka = new Kafka({ + clientId: 'producer-1', + brokers: ['localhost:9092'] +}); + +const consumer = kafka.consumer({ groupId: 'order-status-group-100' }); +const producer = kafka.producer(); + +export async function driveKafka() { + await consumer.connect(); + await producer.connect(); + + await consumer.subscribe({ topic: 'order-topic', fromBeginning: true }); + + await consumer.run({ + eachMessage: async ({ topic, partition, message }) => { + const value = JSON.parse(message.value!.toString()); + console.log('Received command:', value); + }}); + + const order = { + orderId: 'a914817b-c5f0-433e-8280-1cd2fe44234e', + productId: '2adb46de-1c96-4290-a215-9701b0a7900c', + productName: 'Wireless Ergonomic Mouse (Black)', + quantity: 65, + customerEmail: 'johnnydibbert@feil.org', + status: 'SHIPPED' + }; + + await producer.send({ + topic: 'order-topic', + messages: [{ key: order.orderId, value: JSON.stringify(order) }] + }); + + console.log('Published order:', order); +}; + +export async function closeKafka() { + await consumer.stop(); + await consumer.disconnect(); + await producer.disconnect(); +} \ No newline at end of file diff --git a/webui/scripts/dashboard-demo/drive-ldap.ts b/webui/scripts/dashboard-demo/drive-ldap.ts new file mode 100644 index 000000000..79cb3bc85 --- /dev/null +++ b/webui/scripts/dashboard-demo/drive-ldap.ts @@ -0,0 +1,50 @@ +import { Client, Change, Attribute } from 'ldapts'; + +const client = new Client({ url: 'ldap://localhost:8389' }); + +export async function driveLdap() { + await client.bind('dc=hr,dc=example,dc=com'); + + await client.search('dc=hr,dc=example,dc=com', { + filter: '(uid=ajohnson)', + scope: 'sub' + }); + + await client.search('dc=hr,dc=example,dc=com', { + filter: '(memberOf=cn=Sales,ou=departments,dc=hr,dc=example,dc=com)', + scope: 'sub' + }); + + await client.add('uid=cbrown,ou=people,dc=hr,dc=example,dc=com', + { + cn: 'Carol Brown', + uid: 'cbrown', + userPassword: 'secret790', + givenName: 'Carol', + sn: 'Brown', + objectClass: [ 'top', 'person', 'organizationalPerson', 'inetOrgPerson' ] + }, + ); + + await client.modify('uid=bmiller,ou=people,dc=hr,dc=example,dc=com', + new Change({ + operation: 'add', + modification: new Attribute({ + type: 'telephoneNumber', values: ['+1 555 123 9876'] + }) + }), + ); + + await client.compare('uid=bmiller,ou=people,dc=hr,dc=example,dc=com', 'telephoneNumber', '+1 555 123 9876') + + await client.modifyDN('uid=cbrown,ou=people,dc=hr,dc=example,dc=com', 'uid=ctaylor,ou=people,dc=hr,dc=example,dc=com'); + + await client.del('uid=ctaylor,ou=people,dc=hr,dc=example,dc=com'); + + await client.search('dc=hr,dc=example,dc=com', { + filter: '(userAccountControl:1.2.840.113556.1.4.803:=512)', + scope: 'sub' + }); + + await client.unbind(); +} \ No newline at end of file diff --git a/webui/scripts/dashboard-demo/drive-mail.ts b/webui/scripts/dashboard-demo/drive-mail.ts new file mode 100644 index 000000000..fa5dcf50a --- /dev/null +++ b/webui/scripts/dashboard-demo/drive-mail.ts @@ -0,0 +1,229 @@ +import nodemailer from 'nodemailer'; + +const transporter = nodemailer.createTransport({ + host: 'localhost', + port: 8025, + secure: false, + tls: { rejectUnauthorized: false } +}); + +export async function driveMail() { + await sendNewsletter(); + await transporter.close(); + await sendForgetPassword(); + await transporter.close(); +} + +async function sendNewsletter() { + const body = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ New Product Banner +
+

New Arrivals Just Landed!

+
+

+ Fresh styles, cool tech, and exclusive releases — handpicked for you. +

+
+ + + + + + + +
+ Product 1 + +

Smartwatch XT Pro

+

+ Sleek, fast, waterproof — your perfect companion for fitness and daily life. +

+

$199.00

+ + Shop Now + +
+ + + + + + + +
+ Product 2 + +

Wireless Noise-Canceling Headphones

+

+ Crystal-clear sound and long-lasting comfort for work or travel. +

+

$149.00

+ + Shop Now + +
+ + + + + + + +
+ Product 3 + +

Urban Style Backpack

+

+ Durable, lightweight, and perfect for work or weekend adventures. +

+

$79.00

+ + Shop Now + +
+ +
+ You are receiving this newsletter because you subscribed to product updates.
+ © 2025 Your Shop Name. All rights reserved. +
+ + `; + + await transporter.sendMail({ + from: '"Bob Miller" ', + to: '"Alice Johnson" ', + subject: 'Check Out Our New Arrivals!', + html: body, + attachments: [ + { + filename: "header.jpg", + path: "./assets/lead.png", + cid: "headerimg" + }, + { + filename: "product1.jpg", + path: "./assets/watch.png", + cid: "product1" + }, + { + filename: "product2.jpg", + path: "./assets/headphones.png", + cid: "product2" + }, + { + filename: "product3.jpg", + path: "./assets/bag.png", + cid: "product3" + } + ], + auth: { + user: 'bob.miller', + pass: 'mysecretpassword123' + } + }); +} + +async function sendForgetPassword() { + const body = ` + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+

Reset Your Password

+
+ +

+ Hello John Doe, +

+ +

+ We received a request to reset the password for your Example account. + If you made this request, simply click the button below to reset your password: +

+ + + + +

+ If you didn’t request a password reset, you can safely ignore this email. + Your password will remain unchanged. +

+ +

+ Best regards,
+ Your Example Team +

+ +
+ If you’re having trouble with the button above, copy and paste the link into your browser:
+ https://example.com/reset-password?token=YOUR_TOKEN

+ © 2025 Example. All rights reserved. +
+ +
`; + + await transporter.sendMail({ + from: 'zzz@example.com', + to: '"Bob Miller" ', + subject: 'Reset Your Password', + html: body, + }); +} \ No newline at end of file diff --git a/webui/scripts/dashboard-demo/index.ts b/webui/scripts/dashboard-demo/index.ts new file mode 100644 index 000000000..6ab0aecbd --- /dev/null +++ b/webui/scripts/dashboard-demo/index.ts @@ -0,0 +1,29 @@ +import { collectDashboard } from './collect-dashboard.ts'; +import { driveHttp } from './drive-http.ts'; +import { driveKafka, closeKafka } from './drive-kafka.ts'; +import { startMokapi, stopMokapi } from './mokapi.ts'; +import { driveMail } from './drive-mail.ts'; +import { driveLdap } from './drive-ldap.ts'; + +async function main() { + try { + console.log('🚀 Starting Mokapi...'); + await startMokapi(); + + await driveHttp(); + await driveKafka(); + await driveMail(); + await driveLdap(); + + await collectDashboard(); + + } catch (err) { + console.error('❌ Failed to generate demo data') + console.error(err) + } finally { + await closeKafka(); + stopMokapi(); + } +} + +main() \ No newline at end of file diff --git a/webui/scripts/dashboard-demo/mokapi.ts b/webui/scripts/dashboard-demo/mokapi.ts new file mode 100644 index 000000000..99553f898 --- /dev/null +++ b/webui/scripts/dashboard-demo/mokapi.ts @@ -0,0 +1,29 @@ +import { spawn, type ChildProcess } from 'child_process' + +let proc: ChildProcess + +export async function startMokapi() { + proc = spawn('mokapi', ['./demo-configs'], { + stdio: 'inherit', + env: { + ...process.env, + } + }) + + // sleep at least 5sec to get useful memory usage metric + await sleep(5500); + await fetch('http://localhost:8080') +} + +export function stopMokapi() { + if (!proc.killed) { + console.log('🛑 Stopping Mokapi...') + proc.kill('SIGTERM') + } +} + +function sleep(ms: number) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} \ No newline at end of file diff --git a/webui/src/App.vue b/webui/src/App.vue index 11710cc21..570952542 100644 --- a/webui/src/App.vue +++ b/webui/src/App.vue @@ -1,23 +1,6 @@ \ No newline at end of file diff --git a/webui/src/components/dashboard/http/HttpEventParameters.vue b/webui/src/components/dashboard/http/HttpEventParameters.vue index 18caef30d..ecfd29a86 100644 --- a/webui/src/components/dashboard/http/HttpEventParameters.vue +++ b/webui/src/components/dashboard/http/HttpEventParameters.vue @@ -1,7 +1,6 @@ \ No newline at end of file diff --git a/webui/src/components/dashboard/http/HttpPath.vue b/webui/src/components/dashboard/http/HttpPath.vue index e3ef7067d..11e48c928 100644 --- a/webui/src/components/dashboard/http/HttpPath.vue +++ b/webui/src/components/dashboard/http/HttpPath.vue @@ -3,7 +3,6 @@ import { onUnmounted, type PropType } from 'vue' import { useRoute } from '@/router'; import HttpOperationsCard from './HttpOperationsCard.vue'; import Requests from './Requests.vue'; -import '@/assets/http.css' const props = defineProps({ service: { type: Object as PropType, required: true }, @@ -36,15 +35,15 @@ onUnmounted(() => {
-

Path

-

+

Path

+

{{ path.path }}

-

Service

-

+

Service

+

{{ service.name }} diff --git a/webui/src/components/dashboard/http/HttpRequestMetricCard.vue b/webui/src/components/dashboard/http/HttpRequestMetricCard.vue index 09f411270..23f9d9b25 100644 --- a/webui/src/components/dashboard/http/HttpRequestMetricCard.vue +++ b/webui/src/components/dashboard/http/HttpRequestMetricCard.vue @@ -1,18 +1,18 @@ diff --git a/webui/src/components/dashboard/http/HttpServicesCard.vue b/webui/src/components/dashboard/http/HttpServicesCard.vue index 41f06fc67..f593c2383 100644 --- a/webui/src/components/dashboard/http/HttpServicesCard.vue +++ b/webui/src/components/dashboard/http/HttpServicesCard.vue @@ -1,16 +1,16 @@