mirror of
https://github.com/kestra-io/kestra.git
synced 2025-12-25 11:12:12 -05:00
Compare commits
58 Commits
docs/retur
...
v1.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7e17b7114 | ||
|
|
acaee34b0e | ||
|
|
1d78332505 | ||
|
|
7249632510 | ||
|
|
4a66a08c3b | ||
|
|
22fd6e97ea | ||
|
|
9afd86d32b | ||
|
|
797ea6c9e4 | ||
|
|
07d5e815c4 | ||
|
|
33ac9b1495 | ||
|
|
4d5b95d040 | ||
|
|
667aca7345 | ||
|
|
e05cc65202 | ||
|
|
71b606c27c | ||
|
|
47f9f12ce8 | ||
|
|
01acae5e97 | ||
|
|
e5878f08b7 | ||
|
|
0bcb6b4e0d | ||
|
|
3c2ecf4342 | ||
|
|
3d4f66772e | ||
|
|
e2afd4bcc3 | ||
|
|
d143097f03 | ||
|
|
72c0d91c1a | ||
|
|
1d692e56b0 | ||
|
|
0352d617ac | ||
|
|
b41aa4e0b9 | ||
|
|
d811dc030b | ||
|
|
105e62eee1 | ||
|
|
28796862a4 | ||
|
|
637cd794a4 | ||
|
|
fdd5c6e63d | ||
|
|
eda2483ec9 | ||
|
|
7b3c296489 | ||
|
|
fe6f8b4ed9 | ||
|
|
17ff539690 | ||
|
|
bbd0dda47e | ||
|
|
27a8e8b5a7 | ||
|
|
d6620a34cd | ||
|
|
6f8b3c5cfd | ||
|
|
6da6cbab60 | ||
|
|
a899e16178 | ||
|
|
568cd0b0c7 | ||
|
|
92e1dcb6eb | ||
|
|
499e040cd0 | ||
|
|
5916831d62 | ||
|
|
0b1b55957e | ||
|
|
7ee40d376a | ||
|
|
e2c9b3e256 | ||
|
|
556730777b | ||
|
|
c1a75a431f | ||
|
|
4a5b91667a | ||
|
|
f7b2af16a1 | ||
|
|
9351cb22e0 | ||
|
|
b1ecb82fdc | ||
|
|
c6d56151eb | ||
|
|
ed4398467a | ||
|
|
c51947419a | ||
|
|
ccb6a1f4a7 |
4
.github/workflows/auto-translate-ui-keys.yml
vendored
4
.github/workflows/auto-translate-ui-keys.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: "20.x"
|
||||
|
||||
|
||||
2
.github/workflows/e2e.yml
vendored
2
.github/workflows/e2e.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
path: kestra
|
||||
|
||||
# Setup build
|
||||
- uses: kestra-io/actions/.github/actions/setup-build@main
|
||||
- uses: kestra-io/actions/composite/setup-build@main
|
||||
name: Setup - Build
|
||||
id: build
|
||||
with:
|
||||
|
||||
14
.github/workflows/gradle-release-plugins.yml
vendored
14
.github/workflows/gradle-release-plugins.yml
vendored
@@ -25,21 +25,13 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Checkout GitHub Actions
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
repository: kestra-io/actions
|
||||
path: actions
|
||||
ref: main
|
||||
|
||||
# Setup build
|
||||
- uses: ./actions/.github/actions/setup-build
|
||||
- uses: kestra-io/actions/composite/setup-build@main
|
||||
id: build
|
||||
with:
|
||||
java-enabled: true
|
||||
node-enabled: true
|
||||
python-enabled: true
|
||||
caches-enabled: true
|
||||
|
||||
# Get Plugins List
|
||||
- name: Get Plugins List
|
||||
@@ -60,7 +52,7 @@ jobs:
|
||||
GITHUB_PAT: ${{ secrets.GH_PERSONAL_TOKEN }}
|
||||
run: |
|
||||
chmod +x ./dev-tools/release-plugins.sh;
|
||||
|
||||
|
||||
./dev-tools/release-plugins.sh \
|
||||
--release-version=${{github.event.inputs.releaseVersion}} \
|
||||
--next-version=${{github.event.inputs.nextVersion}} \
|
||||
@@ -73,7 +65,7 @@ jobs:
|
||||
GITHUB_PAT: ${{ secrets.GH_PERSONAL_TOKEN }}
|
||||
run: |
|
||||
chmod +x ./dev-tools/release-plugins.sh;
|
||||
|
||||
|
||||
./dev-tools/release-plugins.sh \
|
||||
--release-version=${{github.event.inputs.releaseVersion}} \
|
||||
--next-version=${{github.event.inputs.nextVersion}} \
|
||||
|
||||
9
.github/workflows/gradle-release.yml
vendored
9
.github/workflows/gradle-release.yml
vendored
@@ -38,15 +38,8 @@ jobs:
|
||||
fetch-depth: 0
|
||||
path: kestra
|
||||
|
||||
# Checkout GitHub Actions
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
repository: kestra-io/actions
|
||||
path: actions
|
||||
ref: main
|
||||
|
||||
# Setup build
|
||||
- uses: ./actions/.github/actions/setup-build
|
||||
- uses: kestra-io/actions/composite/setup-build@main
|
||||
id: build
|
||||
with:
|
||||
java-enabled: true
|
||||
|
||||
13
.github/workflows/main.yml
vendored
13
.github/workflows/main.yml
vendored
@@ -59,8 +59,6 @@ jobs:
|
||||
needs:
|
||||
- release
|
||||
if: always()
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
steps:
|
||||
- name: Trigger EE Workflow
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
@@ -70,14 +68,9 @@ jobs:
|
||||
repository: kestra-io/kestra-ee
|
||||
event-type: "oss-updated"
|
||||
|
||||
|
||||
# Slack
|
||||
- name: Slack - Notification
|
||||
uses: Gamesight/slack-workflow-status@master
|
||||
if: ${{ always() && env.SLACK_WEBHOOK_URL != 0 }}
|
||||
if: ${{ failure() && env.SLACK_WEBHOOK_URL != 0 && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') }}
|
||||
uses: kestra-io/actions/composite/slack-status@main
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
name: GitHub Actions
|
||||
icon_emoji: ":github-actions:"
|
||||
channel: "C02DQ1A7JLR" # _int_git channel
|
||||
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
||||
16
.github/workflows/pull-request.yml
vendored
16
.github/workflows/pull-request.yml
vendored
@@ -60,19 +60,3 @@ jobs:
|
||||
name: E2E - Tests
|
||||
uses: ./.github/workflows/e2e.yml
|
||||
|
||||
end:
|
||||
name: End
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
needs: [frontend, backend]
|
||||
steps:
|
||||
# Slack
|
||||
- name: Slack notification
|
||||
uses: Gamesight/slack-workflow-status@master
|
||||
if: ${{ always() && env.SLACK_WEBHOOK_URL != 0 }}
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
name: GitHub Actions
|
||||
icon_emoji: ":github-actions:"
|
||||
channel: "C02DQ1A7JLR"
|
||||
30
.github/workflows/vulnerabilities-check.yml
vendored
30
.github/workflows/vulnerabilities-check.yml
vendored
@@ -21,13 +21,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Checkout GitHub Actions
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
repository: kestra-io/actions
|
||||
path: actions
|
||||
ref: main
|
||||
|
||||
# Setup build
|
||||
- uses: ./actions/.github/actions/setup-build
|
||||
id: build
|
||||
@@ -70,15 +63,8 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Checkout GitHub Actions
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
repository: kestra-io/actions
|
||||
path: actions
|
||||
ref: main
|
||||
|
||||
# Setup build
|
||||
- uses: ./actions/.github/actions/setup-build
|
||||
- uses: kestra-io/actions/composite/setup-build@main
|
||||
id: build
|
||||
with:
|
||||
java-enabled: false
|
||||
@@ -87,7 +73,7 @@ jobs:
|
||||
|
||||
# Run Trivy image scan for Docker vulnerabilities, see https://github.com/aquasecurity/trivy-action
|
||||
- name: Docker Vulnerabilities Check
|
||||
uses: aquasecurity/trivy-action@0.33.0
|
||||
uses: aquasecurity/trivy-action@0.33.1
|
||||
with:
|
||||
image-ref: kestra/kestra:develop
|
||||
format: 'template'
|
||||
@@ -115,24 +101,16 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Checkout GitHub Actions
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
repository: kestra-io/actions
|
||||
path: actions
|
||||
ref: main
|
||||
|
||||
# Setup build
|
||||
- uses: ./actions/.github/actions/setup-build
|
||||
- uses: kestra-io/actions/composite/setup-build@main
|
||||
id: build
|
||||
with:
|
||||
java-enabled: false
|
||||
node-enabled: false
|
||||
caches-enabled: true
|
||||
|
||||
# Run Trivy image scan for Docker vulnerabilities, see https://github.com/aquasecurity/trivy-action
|
||||
- name: Docker Vulnerabilities Check
|
||||
uses: aquasecurity/trivy-action@0.33.0
|
||||
uses: aquasecurity/trivy-action@0.33.1
|
||||
with:
|
||||
image-ref: kestra/kestra:latest
|
||||
format: table
|
||||
|
||||
86
.github/workflows/workflow-backend-test.yml
vendored
86
.github/workflows/workflow-backend-test.yml
vendored
@@ -20,6 +20,7 @@ permissions:
|
||||
contents: write
|
||||
checks: write
|
||||
actions: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -35,7 +36,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
# Setup build
|
||||
- uses: kestra-io/actions/.github/actions/setup-build@main
|
||||
- uses: kestra-io/actions/composite/setup-build@main
|
||||
name: Setup - Build
|
||||
id: build
|
||||
with:
|
||||
@@ -59,84 +60,15 @@ jobs:
|
||||
export GOOGLE_APPLICATION_CREDENTIALS=$HOME/.gcp-service-account.json
|
||||
./gradlew check javadoc --parallel
|
||||
|
||||
# report test
|
||||
- name: Test - Publish Test Results
|
||||
uses: dorny/test-reporter@v2
|
||||
if: always()
|
||||
with:
|
||||
name: Java Tests Report
|
||||
reporter: java-junit
|
||||
path: '**/build/test-results/test/TEST-*.xml'
|
||||
list-suites: 'failed'
|
||||
list-tests: 'failed'
|
||||
fail-on-error: 'false'
|
||||
token: ${{ secrets.GITHUB_AUTH_TOKEN }}
|
||||
|
||||
# Sonar
|
||||
- name: Test - Analyze with Sonar
|
||||
if: env.SONAR_TOKEN != ''
|
||||
- name: comment PR with test report
|
||||
if: ${{ !cancelled() && github.event_name == 'pull_request' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_AUTH_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
shell: bash
|
||||
run: ./gradlew sonar --info
|
||||
run: npx --yes @kestra-io/kestra-devtools generateTestReportSummary --only-errors --ci $(pwd)
|
||||
|
||||
# GCP
|
||||
- name: GCP - Auth with unit test account
|
||||
id: auth
|
||||
if: always() && env.GOOGLE_SERVICE_ACCOUNT != ''
|
||||
continue-on-error: true
|
||||
uses: "google-github-actions/auth@v3"
|
||||
with:
|
||||
credentials_json: "${{ secrets.GOOGLE_SERVICE_ACCOUNT }}"
|
||||
|
||||
- name: GCP - Setup Cloud SDK
|
||||
if: env.GOOGLE_SERVICE_ACCOUNT != ''
|
||||
uses: "google-github-actions/setup-gcloud@v3"
|
||||
|
||||
# Allure check
|
||||
- uses: rlespinasse/github-slug-action@v5
|
||||
name: Allure - Generate slug variables
|
||||
|
||||
- name: Allure - Publish report
|
||||
uses: andrcuns/allure-publish-action@v2.9.0
|
||||
if: always() && env.GOOGLE_SERVICE_ACCOUNT != ''
|
||||
continue-on-error: true
|
||||
env:
|
||||
GITHUB_AUTH_TOKEN: ${{ secrets.GITHUB_AUTH_TOKEN }}
|
||||
JAVA_HOME: /usr/lib/jvm/default-jvm/
|
||||
with:
|
||||
storageType: gcs
|
||||
resultsGlob: "**/build/allure-results"
|
||||
bucket: internal-kestra-host
|
||||
baseUrl: "https://internal.dev.kestra.io"
|
||||
prefix: ${{ format('{0}/{1}', github.repository, 'allure/java') }}
|
||||
copyLatest: true
|
||||
ignoreMissingResults: true
|
||||
|
||||
# Jacoco
|
||||
- name: Jacoco - Copy reports
|
||||
if: env.GOOGLE_SERVICE_ACCOUNT != ''
|
||||
continue-on-error: true
|
||||
shell: bash
|
||||
run: |
|
||||
mv build/reports/jacoco/testCodeCoverageReport build/reports/jacoco/test/
|
||||
mv build/reports/jacoco/test/testCodeCoverageReport.xml build/reports/jacoco/test/jacocoTestReport.xml
|
||||
gsutil -m rsync -d -r build/reports/jacoco/test/ gs://internal-kestra-host/${{ format('{0}/{1}', github.repository, 'jacoco') }}
|
||||
|
||||
# Codecov
|
||||
- name: Codecov - Upload coverage reports
|
||||
uses: codecov/codecov-action@v5
|
||||
# Report Java
|
||||
- name: Report - Java
|
||||
uses: kestra-io/actions/composite/report-java@main
|
||||
if: ${{ !cancelled() }}
|
||||
continue-on-error: true
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
flags: backend
|
||||
|
||||
- name: Codecov - Upload test results
|
||||
uses: codecov/test-results-action@v1
|
||||
if: ${{ !cancelled() }}
|
||||
continue-on-error: true
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
flags: backend
|
||||
secrets: ${{ toJSON(secrets) }}
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
run: npm ci
|
||||
|
||||
# Setup build
|
||||
- uses: kestra-io/actions/.github/actions/setup-build@main
|
||||
- uses: kestra-io/actions/composite/setup-build@main
|
||||
name: Setup - Build
|
||||
id: build
|
||||
with:
|
||||
|
||||
13
.github/workflows/workflow-github-release.yml
vendored
13
.github/workflows/workflow-github-release.yml
vendored
@@ -25,15 +25,6 @@ jobs:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
||||
# Checkout GitHub Actions
|
||||
- name: Checkout - Actions
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: kestra-io/actions
|
||||
sparse-checkout-cone-mode: true
|
||||
path: actions
|
||||
sparse-checkout: |
|
||||
.github/actions
|
||||
|
||||
# Download Exec
|
||||
# Must be done after checkout actions
|
||||
@@ -59,7 +50,7 @@ jobs:
|
||||
|
||||
# GitHub Release
|
||||
- name: Create GitHub release
|
||||
uses: ./actions/.github/actions/github-release
|
||||
uses: kestra-io/actions/composite/github-release@main
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
env:
|
||||
MAKE_LATEST: ${{ steps.is_latest.outputs.latest }}
|
||||
@@ -82,7 +73,7 @@ jobs:
|
||||
|
||||
- name: Merge Release Notes
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
uses: ./actions/.github/actions/github-release-note-merge
|
||||
uses: kestra-io/actions/composite/github-release-note-merge@main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_PERSONAL_TOKEN }}
|
||||
RELEASE_TAG: ${{ github.ref_name }}
|
||||
|
||||
26
.github/workflows/workflow-publish-docker.yml
vendored
26
.github/workflows/workflow-publish-docker.yml
vendored
@@ -11,6 +11,14 @@ on:
|
||||
options:
|
||||
- "true"
|
||||
- "false"
|
||||
retag-lts:
|
||||
description: 'Retag LTS Docker images'
|
||||
required: true
|
||||
type: choice
|
||||
default: "false"
|
||||
options:
|
||||
- "true"
|
||||
- "false"
|
||||
release-tag:
|
||||
description: 'Kestra Release Tag (by default, deduced with the ref)'
|
||||
required: false
|
||||
@@ -179,6 +187,11 @@ jobs:
|
||||
run: |
|
||||
regctl image copy ${{ format('kestra/kestra:{0}{1}', steps.vars.outputs.tag, matrix.image.name) }} ${{ format('kestra/kestra:latest{0}', matrix.image.name) }}
|
||||
|
||||
- name: Retag to LTS
|
||||
if: startsWith(github.ref, 'refs/tags/v') && inputs.retag-lts == 'true'
|
||||
run: |
|
||||
regctl image copy ${{ format('kestra/kestra:{0}{1}', steps.vars.outputs.tag, matrix.image.name) }} ${{ format('kestra/kestra:latest-lts{0}', matrix.image.name) }}
|
||||
|
||||
end:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
@@ -187,14 +200,9 @@ jobs:
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
steps:
|
||||
|
||||
# Slack
|
||||
- name: Slack notification
|
||||
uses: Gamesight/slack-workflow-status@master
|
||||
if: ${{ always() && env.SLACK_WEBHOOK_URL != 0 }}
|
||||
if: ${{ failure() && env.SLACK_WEBHOOK_URL != 0 }}
|
||||
uses: kestra-io/actions/composite/slack-status@main
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
name: GitHub Actions
|
||||
icon_emoji: ':github-actions:'
|
||||
channel: 'C02DQ1A7JLR' # _int_git channel
|
||||
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
||||
|
||||
2
.github/workflows/workflow-publish-maven.yml
vendored
2
.github/workflows/workflow-publish-maven.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
# Setup build
|
||||
- name: Setup - Build
|
||||
uses: kestra-io/actions/.github/actions/setup-build@main
|
||||
uses: kestra-io/actions/composite/setup-build@main
|
||||
id: build
|
||||
with:
|
||||
java-enabled: true
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
publish:
|
||||
name: Pull Request - Delete Docker
|
||||
if: github.repository == github.event.pull_request.head.repo.full_name # prevent running on forks
|
||||
if: github.repository == 'kestra-io/kestra' # prevent running on forks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dataaxiom/ghcr-cleanup-action@v1
|
||||
|
||||
@@ -8,12 +8,12 @@ on:
|
||||
jobs:
|
||||
build-artifacts:
|
||||
name: Build Artifacts
|
||||
if: github.repository == github.event.pull_request.head.repo.full_name # prevent running on forks
|
||||
if: github.repository == 'kestra-io/kestra' # prevent running on forks
|
||||
uses: ./.github/workflows/workflow-build-artifacts.yml
|
||||
|
||||
publish:
|
||||
name: Publish Docker
|
||||
if: github.repository == github.event.pull_request.head.repo.full_name # prevent running on forks
|
||||
if: github.repository == 'kestra-io/kestra' # prevent running on forks
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-artifacts
|
||||
env:
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
|
||||
# Add comment on pull request
|
||||
- name: Add comment to PR
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
||||
14
.github/workflows/workflow-test.yml
vendored
14
.github/workflows/workflow-test.yml
vendored
@@ -84,14 +84,12 @@ jobs:
|
||||
name: Notify - Slack
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ frontend, backend ]
|
||||
if: github.event_name == 'schedule'
|
||||
steps:
|
||||
- name: Notify failed CI
|
||||
id: send-ci-failed
|
||||
if: |
|
||||
always() && (needs.frontend.result != 'success' ||
|
||||
needs.backend.result != 'success')
|
||||
uses: kestra-io/actions/.github/actions/send-ci-failed@main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
always() &&
|
||||
(needs.frontend.result != 'success' || needs.backend.result != 'success') &&
|
||||
(github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop')
|
||||
uses: kestra-io/actions/composite/slack-status@main
|
||||
with:
|
||||
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
||||
@@ -167,6 +167,8 @@ kestra:
|
||||
open-urls:
|
||||
- "/ping"
|
||||
- "/api/v1/executions/webhook/"
|
||||
- "/api/v1/main/executions/webhook/"
|
||||
- "/api/v1/*/executions/webhook/"
|
||||
|
||||
preview:
|
||||
initial-rows: 100
|
||||
|
||||
@@ -3,30 +3,88 @@ package io.kestra.core.events;
|
||||
import io.micronaut.core.annotation.Nullable;
|
||||
import io.micronaut.http.HttpRequest;
|
||||
import io.micronaut.http.context.ServerRequestContext;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@AllArgsConstructor
|
||||
import java.util.Objects;
|
||||
|
||||
@Getter
|
||||
public class CrudEvent<T> {
|
||||
T model;
|
||||
private final T model;
|
||||
@Nullable
|
||||
T previousModel;
|
||||
CrudEventType type;
|
||||
HttpRequest<?> request;
|
||||
|
||||
private final T previousModel;
|
||||
private final CrudEventType type;
|
||||
private final HttpRequest<?> request;
|
||||
|
||||
/**
|
||||
* Static helper method for creating a new {@link CrudEventType#UPDATE} CrudEvent.
|
||||
*
|
||||
* @param model the new created model.
|
||||
* @param <T> type of the model.
|
||||
* @return the new {@link CrudEvent}.
|
||||
*/
|
||||
public static <T> CrudEvent<T> create(T model) {
|
||||
Objects.requireNonNull(model, "Can't create CREATE event with a null model");
|
||||
return new CrudEvent<>(model, null, CrudEventType.CREATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Static helper method for creating a new {@link CrudEventType#DELETE} CrudEvent.
|
||||
*
|
||||
* @param model the deleted model.
|
||||
* @param <T> type of the model.
|
||||
* @return the new {@link CrudEvent}.
|
||||
*/
|
||||
public static <T> CrudEvent<T> delete(T model) {
|
||||
Objects.requireNonNull(model, "Can't create DELETE event with a null model");
|
||||
return new CrudEvent<>(null, model, CrudEventType.DELETE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Static helper method for creating a new CrudEvent.
|
||||
*
|
||||
* @param before the model before the update.
|
||||
* @param after the model after the update.
|
||||
* @param <T> type of the model.
|
||||
* @return the new {@link CrudEvent}.
|
||||
*/
|
||||
public static <T> CrudEvent<T> of(T before, T after) {
|
||||
|
||||
if (before == null && after == null) {
|
||||
throw new IllegalArgumentException("Both before and after cannot be null");
|
||||
}
|
||||
|
||||
if (before == null) {
|
||||
return create(after);
|
||||
}
|
||||
|
||||
if (after == null) {
|
||||
return delete(before);
|
||||
}
|
||||
|
||||
return new CrudEvent<>(after, before, CrudEventType.UPDATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use the static factory methods.
|
||||
*/
|
||||
@Deprecated
|
||||
public CrudEvent(T model, CrudEventType type) {
|
||||
this.model = model;
|
||||
this.type = type;
|
||||
this.previousModel = null;
|
||||
this.request = ServerRequestContext.currentRequest().orElse(null);
|
||||
this(
|
||||
CrudEventType.DELETE.equals(type) ? null : model,
|
||||
CrudEventType.DELETE.equals(type) ? model : null,
|
||||
type,
|
||||
ServerRequestContext.currentRequest().orElse(null)
|
||||
);
|
||||
}
|
||||
|
||||
public CrudEvent(T model, T previousModel, CrudEventType type) {
|
||||
this(model, previousModel, type, ServerRequestContext.currentRequest().orElse(null));
|
||||
}
|
||||
|
||||
public CrudEvent(T model, T previousModel, CrudEventType type, HttpRequest<?> request) {
|
||||
this.model = model;
|
||||
this.previousModel = previousModel;
|
||||
this.type = type;
|
||||
this.request = ServerRequestContext.currentRequest().orElse(null);
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,12 @@ import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Enumeration;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@@ -14,5 +20,59 @@ import java.net.URL;
|
||||
public class ExternalPlugin {
|
||||
private final URL location;
|
||||
private final URL[] resources;
|
||||
private final long crc32;
|
||||
private volatile Long crc32; // lazy-val
|
||||
|
||||
public ExternalPlugin(URL location, URL[] resources) {
|
||||
this.location = location;
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
public Long getCrc32() {
|
||||
if (this.crc32 == null) {
|
||||
synchronized (this) {
|
||||
if (this.crc32 == null) {
|
||||
this.crc32 = computeJarCrc32(location);
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc32;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a CRC32 of the JAR File without reading the whole file
|
||||
*
|
||||
* @param location of the JAR File.
|
||||
* @return the CRC32 of {@code -1} if the checksum can't be computed.
|
||||
*/
|
||||
private static long computeJarCrc32(final URL location) {
|
||||
CRC32 crc = new CRC32();
|
||||
try (JarFile jar = new JarFile(location.toURI().getPath(), false)) {
|
||||
Enumeration<JarEntry> entries = jar.entries();
|
||||
byte[] buffer = new byte[Long.BYTES]; // reusable buffer to avoid re-allocation
|
||||
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
crc.update(entry.getName().getBytes(StandardCharsets.UTF_8));
|
||||
updateCrc32WithLong(crc, buffer, entry.getSize());
|
||||
updateCrc32WithLong(crc, buffer, entry.getCrc());
|
||||
}
|
||||
|
||||
return crc.getValue();
|
||||
} catch (Exception e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateCrc32WithLong(CRC32 crc32, byte[] reusable, long val) {
|
||||
// fast long -> byte conversion
|
||||
reusable[0] = (byte) (val >>> 56);
|
||||
reusable[1] = (byte) (val >>> 48);
|
||||
reusable[2] = (byte) (val >>> 40);
|
||||
reusable[3] = (byte) (val >>> 32);
|
||||
reusable[4] = (byte) (val >>> 24);
|
||||
reusable[5] = (byte) (val >>> 16);
|
||||
reusable[6] = (byte) (val >>> 8);
|
||||
reusable[7] = (byte) val;
|
||||
crc32.update(reusable);;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ public class PluginClassLoader extends URLClassLoader {
|
||||
+ "|dev.failsafe"
|
||||
+ "|reactor"
|
||||
+ "|io.opentelemetry"
|
||||
+ "|io.netty"
|
||||
+ ")\\..*$");
|
||||
|
||||
private final ClassLoader parent;
|
||||
|
||||
@@ -51,8 +51,7 @@ public class PluginResolver {
|
||||
final List<URL> resources = resolveUrlsForPluginPath(path);
|
||||
plugins.add(new ExternalPlugin(
|
||||
path.toUri().toURL(),
|
||||
resources.toArray(new URL[0]),
|
||||
computeJarCrc32(path)
|
||||
resources.toArray(new URL[0])
|
||||
));
|
||||
}
|
||||
} catch (final InvalidPathException | MalformedURLException e) {
|
||||
@@ -124,33 +123,5 @@ public class PluginResolver {
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compute a CRC32 of the JAR File without reading the whole file
|
||||
*
|
||||
* @param location of the JAR File.
|
||||
* @return the CRC32 of {@code -1} if the checksum can't be computed.
|
||||
*/
|
||||
private static long computeJarCrc32(final Path location) {
|
||||
CRC32 crc = new CRC32();
|
||||
try (JarFile jar = new JarFile(location.toFile(), false)) {
|
||||
Enumeration<JarEntry> entries = jar.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
crc.update(entry.getName().getBytes());
|
||||
crc.update(longToBytes(entry.getSize()));
|
||||
crc.update(longToBytes(entry.getCrc()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return -1;
|
||||
}
|
||||
return crc.getValue();
|
||||
}
|
||||
|
||||
private static byte[] longToBytes(long x) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
|
||||
buffer.putLong(x);
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import io.kestra.core.models.flows.FlowInterface;
|
||||
import io.kestra.core.models.flows.Input;
|
||||
import io.kestra.core.models.flows.State;
|
||||
import io.kestra.core.models.flows.input.SecretInput;
|
||||
import io.kestra.core.models.property.Property;
|
||||
import io.kestra.core.models.property.PropertyContext;
|
||||
import io.kestra.core.models.tasks.Task;
|
||||
import io.kestra.core.models.triggers.AbstractTrigger;
|
||||
@@ -282,15 +283,15 @@ public final class RunVariables {
|
||||
|
||||
if (flow != null && flow.getInputs() != null) {
|
||||
// we add default inputs value from the flow if not already set, this will be useful for triggers
|
||||
flow.getInputs().stream()
|
||||
.filter(input -> input.getDefaults() != null && !inputs.containsKey(input.getId()))
|
||||
.forEach(input -> {
|
||||
try {
|
||||
inputs.put(input.getId(), FlowInputOutput.resolveDefaultValue(input, propertyContext));
|
||||
} catch (IllegalVariableEvaluationException e) {
|
||||
throw new RuntimeException("Unable to inject default value for input '" + input.getId() + "'", e);
|
||||
}
|
||||
});
|
||||
flow.getInputs().stream()
|
||||
.filter(input -> input.getDefaults() != null && !inputs.containsKey(input.getId()))
|
||||
.forEach(input -> {
|
||||
try {
|
||||
inputs.put(input.getId(), FlowInputOutput.resolveDefaultValue(input, propertyContext));
|
||||
} catch (IllegalVariableEvaluationException e) {
|
||||
// Silent catch, if an input depends on another input, or a variable that is populated at runtime / input filling time, we can't resolve it here.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!inputs.isEmpty()) {
|
||||
|
||||
@@ -45,7 +45,7 @@ final class Secret {
|
||||
for (var entry: data.entrySet()) {
|
||||
if (entry.getValue() instanceof Map map) {
|
||||
// if some value are of type EncryptedString we decode them and replace the object
|
||||
if (EncryptedString.TYPE.equalsIgnoreCase((String)map.get("type"))) {
|
||||
if (map.get("type") instanceof String typeStr && EncryptedString.TYPE.equalsIgnoreCase(typeStr)) {
|
||||
try {
|
||||
String decoded = decrypt((String) map.get("value"));
|
||||
decryptedMap.put(entry.getKey(), decoded);
|
||||
|
||||
@@ -163,31 +163,28 @@ public final class JacksonMapper {
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Pair<JsonNode, JsonNode> getBiDirectionalDiffs(Object previous, Object current) {
|
||||
JsonNode previousJson = MAPPER.valueToTree(previous);
|
||||
JsonNode newJson = MAPPER.valueToTree(current);
|
||||
public static Pair<JsonNode, JsonNode> getBiDirectionalDiffs(Object before, Object after) {
|
||||
JsonNode beforeNode = MAPPER.valueToTree(before);
|
||||
JsonNode afterNode = MAPPER.valueToTree(after);
|
||||
|
||||
JsonNode patchPrevToNew = JsonDiff.asJson(previousJson, newJson);
|
||||
JsonNode patchNewToPrev = JsonDiff.asJson(newJson, previousJson);
|
||||
JsonNode patch = JsonDiff.asJson(beforeNode, afterNode);
|
||||
JsonNode revert = JsonDiff.asJson(afterNode, beforeNode);
|
||||
|
||||
return Pair.of(patchPrevToNew, patchNewToPrev);
|
||||
return Pair.of(patch, revert);
|
||||
}
|
||||
|
||||
public static String applyPatches(Object object, List<JsonNode> patches) throws JsonProcessingException {
|
||||
|
||||
public static JsonNode applyPatchesOnJsonNode(JsonNode jsonObject, List<JsonNode> patches) {
|
||||
for (JsonNode patch : patches) {
|
||||
try {
|
||||
// Required for ES
|
||||
if (patch.findValue("value") == null) {
|
||||
((ObjectNode) patch.get(0)).set("value", (JsonNode) null);
|
||||
if (patch.findValue("value") == null && !patch.isEmpty()) {
|
||||
((ObjectNode) patch.get(0)).set("value", null);
|
||||
}
|
||||
JsonNode current = MAPPER.valueToTree(object);
|
||||
object = JsonPatch.fromJson(patch).apply(current);
|
||||
jsonObject = JsonPatch.fromJson(patch).apply(jsonObject);
|
||||
} catch (IOException | JsonPatchException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return MAPPER.writeValueAsString(object);
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,12 +3,7 @@ package io.kestra.core.services;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import io.kestra.core.exceptions.FlowProcessingException;
|
||||
import io.kestra.core.models.executions.Execution;
|
||||
import io.kestra.core.models.flows.Flow;
|
||||
import io.kestra.core.models.flows.FlowId;
|
||||
import io.kestra.core.models.flows.FlowInterface;
|
||||
import io.kestra.core.models.flows.FlowWithException;
|
||||
import io.kestra.core.models.flows.FlowWithSource;
|
||||
import io.kestra.core.models.flows.GenericFlow;
|
||||
import io.kestra.core.models.flows.*;
|
||||
import io.kestra.core.models.tasks.RunnableTask;
|
||||
import io.kestra.core.models.topologies.FlowTopology;
|
||||
import io.kestra.core.models.triggers.AbstractTrigger;
|
||||
@@ -30,16 +25,7 @@ import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -551,23 +537,24 @@ public class FlowService {
|
||||
return expandAll ? recursiveFlowTopology(new ArrayList<>(), tenant, namespace, id, destinationOnly) : flowTopologyRepository.get().findByFlow(tenant, namespace, id, destinationOnly).stream();
|
||||
}
|
||||
|
||||
private Stream<FlowTopology> recursiveFlowTopology(List<FlowId> flowIds, String tenantId, String namespace, String id, boolean destinationOnly) {
|
||||
private Stream<FlowTopology> recursiveFlowTopology(List<String> visitedTopologies, String tenantId, String namespace, String id, boolean destinationOnly) {
|
||||
if (flowTopologyRepository.isEmpty()) {
|
||||
throw noRepositoryException();
|
||||
}
|
||||
|
||||
List<FlowTopology> flowTopologies = flowTopologyRepository.get().findByFlow(tenantId, namespace, id, destinationOnly);
|
||||
|
||||
FlowId flowId = FlowId.of(tenantId, namespace, id, null);
|
||||
if (flowIds.contains(flowId)) {
|
||||
return flowTopologies.stream();
|
||||
}
|
||||
flowIds.add(flowId);
|
||||
var flowTopologies = flowTopologyRepository.get().findByFlow(tenantId, namespace, id, destinationOnly);
|
||||
|
||||
return flowTopologies.stream()
|
||||
.flatMap(topology -> Stream.of(topology.getDestination(), topology.getSource()))
|
||||
// recursively fetch child nodes
|
||||
.flatMap(node -> recursiveFlowTopology(flowIds, node.getTenantId(), node.getNamespace(), node.getId(), destinationOnly));
|
||||
// ignore already visited topologies
|
||||
.filter(x -> !visitedTopologies.contains(x.uid()))
|
||||
.flatMap(topology -> {
|
||||
visitedTopologies.add(topology.uid());
|
||||
Stream<FlowTopology> subTopologies = Stream
|
||||
.of(topology.getDestination(), topology.getSource())
|
||||
// recursively visit children and parents nodes
|
||||
.flatMap(relationNode -> recursiveFlowTopology(visitedTopologies, relationNode.getTenantId(), relationNode.getNamespace(), relationNode.getId(), destinationOnly));
|
||||
return Stream.concat(Stream.of(topology), subTopologies);
|
||||
});
|
||||
}
|
||||
|
||||
private IllegalStateException noRepositoryException() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.kestra.core.storages.kv;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.time.Duration;
|
||||
@@ -9,6 +10,7 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public class KVMetadata {
|
||||
private String description;
|
||||
private Instant expirationDate;
|
||||
@@ -17,14 +19,18 @@ public class KVMetadata {
|
||||
if (ttl != null && ttl.isNegative()) {
|
||||
throw new IllegalArgumentException("ttl cannot be negative");
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.description = description;
|
||||
if (ttl != null) {
|
||||
this.expirationDate = Instant.now().plus(ttl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public KVMetadata(String description, Instant expirationDate) {
|
||||
this.description = description;
|
||||
this.expirationDate = expirationDate;
|
||||
}
|
||||
|
||||
public KVMetadata(Map<String, String> metadata) {
|
||||
if (metadata == null) {
|
||||
return;
|
||||
@@ -46,4 +52,9 @@ public class KVMetadata {
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[description=" + description + ", expirationDate=" + expirationDate + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ import io.kestra.core.exceptions.ResourceExpiredException;
|
||||
import io.kestra.core.storages.StorageContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -104,8 +106,33 @@ public interface KVStore {
|
||||
default boolean exists(String key) throws IOException {
|
||||
return list().stream().anyMatch(kvEntry -> kvEntry.key().equals(key));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Finds a KV entry with associated metadata for a given key.
|
||||
*
|
||||
* @param key the KV entry key.
|
||||
* @return an optional of {@link KVValueAndMetadata}.
|
||||
*
|
||||
* @throws UncheckedIOException if an error occurred while executing the operation on the K/V store.
|
||||
*/
|
||||
default Optional<KVValueAndMetadata> findMetadataAndValue(final String key) throws UncheckedIOException {
|
||||
try {
|
||||
return get(key).flatMap(entry ->
|
||||
{
|
||||
try {
|
||||
return getValue(entry.key()).map(current -> new KVValueAndMetadata(new KVMetadata(entry.description(), entry.expirationDate()), current.value()));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
} catch (ResourceExpiredException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
Pattern KEY_VALIDATOR_PATTERN = Pattern.compile("[a-zA-Z0-9][a-zA-Z0-9._-]*");
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,7 @@ import io.kestra.core.repositories.FlowRepositoryInterface;
|
||||
import io.kestra.core.repositories.FlowTopologyRepositoryInterface;
|
||||
import io.kestra.core.services.ConditionService;
|
||||
import io.kestra.core.utils.ListUtils;
|
||||
import io.kestra.core.utils.MapUtils;
|
||||
import io.kestra.plugin.core.condition.*;
|
||||
import io.micronaut.core.annotation.Nullable;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -175,9 +176,6 @@ public class FlowTopologyService {
|
||||
protected boolean isTriggerChild(Flow parent, Flow child) {
|
||||
List<AbstractTrigger> triggers = ListUtils.emptyOnNull(child.getTriggers());
|
||||
|
||||
// simulated execution: we add a "simulated" label so conditions can know that the evaluation is for a simulated execution
|
||||
Execution execution = Execution.newExecution(parent, (f, e) -> null, List.of(SIMULATED_EXECUTION), Optional.empty());
|
||||
|
||||
// keep only flow trigger
|
||||
List<io.kestra.plugin.core.trigger.Flow> flowTriggers = triggers
|
||||
.stream()
|
||||
@@ -189,13 +187,16 @@ public class FlowTopologyService {
|
||||
return false;
|
||||
}
|
||||
|
||||
// simulated execution: we add a "simulated" label so conditions can know that the evaluation is for a simulated execution
|
||||
Execution execution = Execution.newExecution(parent, (f, e) -> null, List.of(SIMULATED_EXECUTION), Optional.empty());
|
||||
|
||||
boolean conditionMatch = flowTriggers
|
||||
.stream()
|
||||
.flatMap(flow -> ListUtils.emptyOnNull(flow.getConditions()).stream())
|
||||
.allMatch(condition -> validateCondition(condition, parent, execution));
|
||||
|
||||
boolean preconditionMatch = flowTriggers.stream()
|
||||
.anyMatch(flow -> flow.getPreconditions() == null || validateMultipleConditions(flow.getPreconditions().getConditions(), parent, execution));
|
||||
.anyMatch(flow -> flow.getPreconditions() == null || validatePreconditions(flow.getPreconditions(), parent, execution));
|
||||
|
||||
return conditionMatch && preconditionMatch;
|
||||
}
|
||||
@@ -239,11 +240,24 @@ public class FlowTopologyService {
|
||||
}
|
||||
|
||||
private boolean isMandatoryMultipleCondition(Condition condition) {
|
||||
return Stream
|
||||
.of(
|
||||
Expression.class
|
||||
)
|
||||
.anyMatch(aClass -> condition.getClass().isAssignableFrom(aClass));
|
||||
return condition.getClass().isAssignableFrom(Expression.class);
|
||||
}
|
||||
|
||||
private boolean validatePreconditions(io.kestra.plugin.core.trigger.Flow.Preconditions preconditions, FlowInterface child, Execution execution) {
|
||||
boolean upstreamFlowMatched = MapUtils.emptyOnNull(preconditions.getUpstreamFlowsConditions())
|
||||
.values()
|
||||
.stream()
|
||||
.filter(c -> !isFilterCondition(c))
|
||||
.anyMatch(c -> validateCondition(c, child, execution));
|
||||
|
||||
boolean whereMatched = MapUtils.emptyOnNull(preconditions.getWhereConditions())
|
||||
.values()
|
||||
.stream()
|
||||
.filter(c -> !isFilterCondition(c))
|
||||
.allMatch(c -> validateCondition(c, child, execution));
|
||||
|
||||
// to be a dependency, if upstream flow is set it must be either inside it so it's a AND between upstream flow and where
|
||||
return upstreamFlowMatched && whereMatched;
|
||||
}
|
||||
|
||||
private boolean isFilterCondition(Condition condition) {
|
||||
|
||||
@@ -206,22 +206,17 @@ public class MapUtils {
|
||||
|
||||
/**
|
||||
* Utility method that flatten a nested map.
|
||||
* <p>
|
||||
* NOTE: for simplicity, this method didn't allow to flatten maps with conflicting keys that would end up in different flatten keys,
|
||||
* this could be related later if needed by flattening {k1: k2: {k3: v1}, k1: {k4: v2}} to {k1.k2.k3: v1, k1.k4: v2} is prohibited for now.
|
||||
*
|
||||
* @param nestedMap the nested map.
|
||||
* @return the flattened map.
|
||||
*
|
||||
* @throws IllegalArgumentException if any entry contains a map of more than one element.
|
||||
*/
|
||||
public static Map<String, Object> nestedToFlattenMap(@NotNull Map<String, Object> nestedMap) {
|
||||
Map<String, Object> result = new TreeMap<>();
|
||||
|
||||
for (Map.Entry<String, Object> entry : nestedMap.entrySet()) {
|
||||
if (entry.getValue() instanceof Map<?, ?> map) {
|
||||
Map.Entry<String, Object> flatten = flattenEntry(entry.getKey(), (Map<String, Object>) map);
|
||||
result.put(flatten.getKey(), flatten.getValue());
|
||||
Map<String, Object> flatten = flattenEntry(entry.getKey(), (Map<String, Object>) map);
|
||||
result.putAll(flatten);
|
||||
} else {
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
@@ -229,18 +224,19 @@ public class MapUtils {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Map.Entry<String, Object> flattenEntry(String key, Map<String, Object> value) {
|
||||
if (value.size() > 1) {
|
||||
throw new IllegalArgumentException("You cannot flatten a map with an entry that is a map of more than one element, conflicting key: " + key);
|
||||
private static Map<String, Object> flattenEntry(String key, Map<String, Object> value) {
|
||||
Map<String, Object> result = new TreeMap<>();
|
||||
|
||||
for (Map.Entry<String, Object> entry : value.entrySet()) {
|
||||
String newKey = key + "." + entry.getKey();
|
||||
Object newValue = entry.getValue();
|
||||
if (newValue instanceof Map<?, ?> map) {
|
||||
result.putAll(flattenEntry(newKey, (Map<String, Object>) map));
|
||||
} else {
|
||||
result.put(newKey, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
Map.Entry<String, Object> entry = value.entrySet().iterator().next();
|
||||
String newKey = key + "." + entry.getKey();
|
||||
Object newValue = entry.getValue();
|
||||
if (newValue instanceof Map<?, ?> map) {
|
||||
return flattenEntry(newKey, (Map<String, Object>) map);
|
||||
} else {
|
||||
return Map.entry(newKey, newValue);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -68,6 +69,7 @@ import java.util.Optional;
|
||||
)
|
||||
}
|
||||
)
|
||||
@Slf4j
|
||||
public class Exit extends Task implements ExecutionUpdatableTask {
|
||||
@NotNull
|
||||
@Schema(
|
||||
@@ -104,12 +106,13 @@ public class Exit extends Task implements ExecutionUpdatableTask {
|
||||
// ends all parents
|
||||
while (newTaskRun.getParentTaskRunId() != null) {
|
||||
newTaskRun = newExecution.findTaskRunByTaskRunId(newTaskRun.getParentTaskRunId()).withState(exitState);
|
||||
newExecution = execution.withTaskRun(newTaskRun);
|
||||
newExecution = newExecution.withTaskRun(newTaskRun);
|
||||
}
|
||||
return newExecution;
|
||||
} catch (InternalException e) {
|
||||
// in case we cannot update the last not terminated task run, we ignore it
|
||||
return execution;
|
||||
log.warn("Unable to update the taskrun state", e);
|
||||
return execution.withState(exitState);
|
||||
}
|
||||
})
|
||||
.orElse(execution)
|
||||
|
||||
@@ -216,49 +216,46 @@ public class Subflow extends Task implements ExecutableTask<Subflow.Output>, Chi
|
||||
|
||||
VariablesService variablesService = ((DefaultRunContext) runContext).getApplicationContext().getBean(VariablesService.class);
|
||||
if (this.wait) { // we only compute outputs if we wait for the subflow
|
||||
boolean isOutputsAllowed = runContext
|
||||
.<Boolean>pluginConfiguration(PLUGIN_FLOW_OUTPUTS_ENABLED)
|
||||
.orElse(true);
|
||||
|
||||
List<io.kestra.core.models.flows.Output> subflowOutputs = flow.getOutputs();
|
||||
|
||||
|
||||
// region [deprecated] Subflow outputs feature
|
||||
if (subflowOutputs == null && isOutputsAllowed && this.getOutputs() != null) {
|
||||
subflowOutputs = this.getOutputs().entrySet().stream()
|
||||
.<io.kestra.core.models.flows.Output>map(entry -> io.kestra.core.models.flows.Output
|
||||
.builder()
|
||||
.id(entry.getKey())
|
||||
.value(entry.getValue())
|
||||
.required(true)
|
||||
.build()
|
||||
)
|
||||
.toList();
|
||||
if (subflowOutputs == null && this.getOutputs() != null) {
|
||||
boolean isOutputsAllowed = runContext
|
||||
.<Boolean>pluginConfiguration(PLUGIN_FLOW_OUTPUTS_ENABLED)
|
||||
.orElse(true);
|
||||
if (isOutputsAllowed) {
|
||||
try {
|
||||
subflowOutputs = this.getOutputs().entrySet().stream()
|
||||
.<io.kestra.core.models.flows.Output>map(entry -> io.kestra.core.models.flows.Output
|
||||
.builder()
|
||||
.id(entry.getKey())
|
||||
.value(entry.getValue())
|
||||
.required(true)
|
||||
.build()
|
||||
)
|
||||
.toList();
|
||||
} catch (Exception e) {
|
||||
Variables variables = variablesService.of(StorageContext.forTask(taskRun), builder.build());
|
||||
return failSubflowDueToOutput(runContext, taskRun, execution, e, variables);
|
||||
}
|
||||
} else {
|
||||
runContext.logger().warn("Defining outputs inside the Subflow task is not allowed.");
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
if (subflowOutputs != null && !subflowOutputs.isEmpty()) {
|
||||
try {
|
||||
Map<String, Object> outputs = FlowInputOutput.renderFlowOutputs(subflowOutputs, runContext);
|
||||
|
||||
Map<String, Object> rOutputs = FlowInputOutput.renderFlowOutputs(subflowOutputs, runContext);
|
||||
|
||||
FlowInputOutput flowInputOutput = ((DefaultRunContext)runContext).getApplicationContext().getBean(FlowInputOutput.class); // this is hacking
|
||||
if (flow.getOutputs() != null && flowInputOutput != null) {
|
||||
outputs = flowInputOutput.typedOutputs(flow, execution, outputs);
|
||||
rOutputs = flowInputOutput.typedOutputs(flow, execution, rOutputs);
|
||||
}
|
||||
builder.outputs(outputs);
|
||||
builder.outputs(rOutputs);
|
||||
} catch (Exception e) {
|
||||
runContext.logger().warn("Failed to extract outputs with the error: '{}'", e.getLocalizedMessage(), e);
|
||||
var state = State.Type.fail(this);
|
||||
Variables variables = variablesService.of(StorageContext.forTask(taskRun), builder.build());
|
||||
taskRun = taskRun
|
||||
.withState(state)
|
||||
.withAttempts(Collections.singletonList(TaskRunAttempt.builder().state(new State().withState(state)).build()))
|
||||
.withOutputs(variables);
|
||||
|
||||
return Optional.of(SubflowExecutionResult.builder()
|
||||
.executionId(execution.getId())
|
||||
.state(State.Type.FAILED)
|
||||
.parentTaskRun(taskRun)
|
||||
.build());
|
||||
return failSubflowDueToOutput(runContext, taskRun, execution, e, variables);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -282,6 +279,21 @@ public class Subflow extends Task implements ExecutableTask<Subflow.Output>, Chi
|
||||
return Optional.of(ExecutableUtils.subflowExecutionResult(taskRun, execution));
|
||||
}
|
||||
|
||||
private Optional<SubflowExecutionResult> failSubflowDueToOutput(RunContext runContext, TaskRun taskRun, Execution execution, Exception e, Variables outputs) {
|
||||
runContext.logger().error("Failed to extract outputs with the error: '{}'", e.getLocalizedMessage(), e);
|
||||
var state = State.Type.fail(this);
|
||||
taskRun = taskRun
|
||||
.withState(state)
|
||||
.withAttempts(Collections.singletonList(TaskRunAttempt.builder().state(new State().withState(state)).build()))
|
||||
.withOutputs(outputs);
|
||||
|
||||
return Optional.of(SubflowExecutionResult.builder()
|
||||
.executionId(execution.getId())
|
||||
.state(State.Type.FAILED)
|
||||
.parentTaskRun(taskRun)
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean waitForExecution() {
|
||||
return this.wait;
|
||||
|
||||
@@ -202,7 +202,7 @@ import static io.kestra.core.utils.Rethrow.throwPredicate;
|
||||
code = """
|
||||
id: sentry_execution_example
|
||||
namespace: company.team
|
||||
|
||||
|
||||
tasks:
|
||||
- id: send_alert
|
||||
type: io.kestra.plugin.notifications.sentry.SentryExecution
|
||||
@@ -221,7 +221,7 @@ import static io.kestra.core.utils.Rethrow.throwPredicate;
|
||||
- WARNING
|
||||
- type: io.kestra.plugin.core.condition.ExecutionNamespace
|
||||
namespace: company.payroll
|
||||
prefix: false"""
|
||||
prefix: false"""
|
||||
)
|
||||
|
||||
},
|
||||
@@ -405,6 +405,28 @@ public class Flow extends AbstractTrigger implements TriggerOutput<Flow.Output>
|
||||
return conditions;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Map<String, Condition> getUpstreamFlowsConditions() {
|
||||
AtomicInteger conditionId = new AtomicInteger();
|
||||
return ListUtils.emptyOnNull(flows).stream()
|
||||
.map(upstreamFlow -> Map.entry(
|
||||
"condition_" + conditionId.incrementAndGet(),
|
||||
new UpstreamFlowCondition(upstreamFlow)
|
||||
))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Map<String, Condition> getWhereConditions() {
|
||||
AtomicInteger conditionId = new AtomicInteger();
|
||||
return ListUtils.emptyOnNull(where).stream()
|
||||
.map(filter -> Map.entry(
|
||||
"condition_" + conditionId.incrementAndGet() + "_" + filter.getId(),
|
||||
new FilterCondition(filter)
|
||||
))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger logger() {
|
||||
return log;
|
||||
|
||||
121
core/src/test/java/io/kestra/core/events/CrudEventTest.java
Normal file
121
core/src/test/java/io/kestra/core/events/CrudEventTest.java
Normal file
@@ -0,0 +1,121 @@
|
||||
package io.kestra.core.events;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
|
||||
|
||||
class CrudEventTest {
|
||||
|
||||
@Test
|
||||
void shouldReturnCreateEventWhenModelIsProvided() {
|
||||
// Given
|
||||
String model = "testModel";
|
||||
|
||||
// When
|
||||
CrudEvent<String> event = CrudEvent.create(model);
|
||||
|
||||
// Then
|
||||
assertThat(event.getModel()).isEqualTo(model);
|
||||
assertThat(event.getPreviousModel()).isNull();
|
||||
assertThat(event.getType()).isEqualTo(CrudEventType.CREATE);
|
||||
assertThat(event.getRequest()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowExceptionWhenCreateEventWithNullModel() {
|
||||
// Given
|
||||
String model = null;
|
||||
|
||||
// When / Then
|
||||
assertThatThrownBy(() -> CrudEvent.create(model))
|
||||
.isInstanceOf(NullPointerException.class)
|
||||
.hasMessage("Can't create CREATE event with a null model");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnDeleteEventWhenModelIsProvided() {
|
||||
// Given
|
||||
String model = "testModel";
|
||||
|
||||
// When
|
||||
CrudEvent<String> event = CrudEvent.delete(model);
|
||||
|
||||
// Then
|
||||
assertThat(event.getModel()).isNull();
|
||||
assertThat(event.getPreviousModel()).isEqualTo(model);
|
||||
assertThat(event.getType()).isEqualTo(CrudEventType.DELETE);
|
||||
assertThat(event.getRequest()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowExceptionWhenDeleteEventWithNullModel() {
|
||||
// Given
|
||||
String model = null;
|
||||
|
||||
// When / Then
|
||||
assertThatThrownBy(() -> CrudEvent.delete(model))
|
||||
.isInstanceOf(NullPointerException.class)
|
||||
.hasMessage("Can't create DELETE event with a null model");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnUpdateEventWhenBeforeAndAfterAreProvided() {
|
||||
// Given
|
||||
String before = "oldModel";
|
||||
String after = "newModel";
|
||||
|
||||
// When
|
||||
CrudEvent<String> event = CrudEvent.of(before, after);
|
||||
|
||||
// Then
|
||||
assertThat(event.getModel()).isEqualTo(after);
|
||||
assertThat(event.getPreviousModel()).isEqualTo(before);
|
||||
assertThat(event.getType()).isEqualTo(CrudEventType.UPDATE);
|
||||
assertThat(event.getRequest()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnCreateEventWhenBeforeIsNullAndAfterIsProvided() {
|
||||
// Given
|
||||
String before = null;
|
||||
String after = "newModel";
|
||||
|
||||
// When
|
||||
CrudEvent<String> event = CrudEvent.of(before, after);
|
||||
|
||||
// Then
|
||||
assertThat(event.getModel()).isEqualTo(after);
|
||||
assertThat(event.getPreviousModel()).isNull();
|
||||
assertThat(event.getType()).isEqualTo(CrudEventType.CREATE);
|
||||
assertThat(event.getRequest()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnDeleteEventWhenAfterIsNullAndBeforeIsProvided() {
|
||||
// Given
|
||||
String before = "oldModel";
|
||||
String after = null;
|
||||
|
||||
// When
|
||||
CrudEvent<String> event = CrudEvent.of(before, after);
|
||||
|
||||
// Then
|
||||
assertThat(event.getModel()).isNull();
|
||||
assertThat(event.getPreviousModel()).isEqualTo(before);
|
||||
assertThat(event.getType()).isEqualTo(CrudEventType.DELETE);
|
||||
assertThat(event.getRequest()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowExceptionWhenBothBeforeAndAfterAreNull() {
|
||||
// Given
|
||||
String before = null;
|
||||
String after = null;
|
||||
|
||||
// When / Then
|
||||
assertThatThrownBy(() -> CrudEvent.of(before, after))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Both before and after cannot be null");
|
||||
}
|
||||
}
|
||||
@@ -453,6 +453,12 @@ public abstract class AbstractRunnerTest {
|
||||
flowConcurrencyCaseTest.flowConcurrencyQueueAfterExecution();
|
||||
}
|
||||
|
||||
@Test
|
||||
@LoadFlows({"flows/valids/flow-concurrency-subflow.yml", "flows/valids/flow-concurrency-cancel.yml"})
|
||||
void flowConcurrencySubflow() throws Exception {
|
||||
flowConcurrencyCaseTest.flowConcurrencySubflow();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExecuteFlow("flows/valids/executable-fail.yml")
|
||||
void badExecutable(Execution execution) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import io.kestra.core.models.flows.State.Type;
|
||||
import io.kestra.core.queues.QueueException;
|
||||
import io.kestra.core.queues.QueueFactoryInterface;
|
||||
import io.kestra.core.queues.QueueInterface;
|
||||
import io.kestra.core.reporter.model.Count;
|
||||
import io.kestra.core.repositories.FlowRepositoryInterface;
|
||||
import io.kestra.core.services.ExecutionService;
|
||||
import io.kestra.core.storages.StorageInterface;
|
||||
@@ -391,6 +392,52 @@ public class FlowConcurrencyCaseTest {
|
||||
assertThat(executionResult2.get().getState().getHistories().get(2).getState()).isEqualTo(State.Type.RUNNING);
|
||||
}
|
||||
|
||||
public void flowConcurrencySubflow() throws TimeoutException, QueueException, InterruptedException {
|
||||
CountDownLatch successLatch = new CountDownLatch(1);
|
||||
CountDownLatch canceledLatch = new CountDownLatch(1);
|
||||
Flux<Execution> receive = TestsUtils.receive(executionQueue, e -> {
|
||||
if (e.getLeft().getFlowId().equals("flow-concurrency-cancel")) {
|
||||
if (e.getLeft().getState().getCurrent() == State.Type.SUCCESS) {
|
||||
successLatch.countDown();
|
||||
}
|
||||
if (e.getLeft().getState().getCurrent() == Type.CANCELLED) {
|
||||
canceledLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME we should fail if we receive the cancel execution again but on Kafka it happens
|
||||
});
|
||||
|
||||
Execution execution1 = runnerUtils.runOneUntilRunning(MAIN_TENANT, "io.kestra.tests", "flow-concurrency-subflow", null, null, Duration.ofSeconds(30));
|
||||
Execution execution2 = runnerUtils.runOne(MAIN_TENANT, "io.kestra.tests", "flow-concurrency-subflow");
|
||||
|
||||
assertThat(execution1.getState().isRunning()).isTrue();
|
||||
assertThat(execution2.getState().getCurrent()).isEqualTo(Type.SUCCESS);
|
||||
|
||||
// assert we have one canceled subflow and one in success
|
||||
assertTrue(canceledLatch.await(1, TimeUnit.MINUTES));
|
||||
assertTrue(successLatch.await(1, TimeUnit.MINUTES));
|
||||
receive.blockLast();
|
||||
|
||||
// run another execution to be sure that everything work (purge is correctly done)
|
||||
CountDownLatch newSuccessLatch = new CountDownLatch(1);
|
||||
Flux<Execution> secondReceive = TestsUtils.receive(executionQueue, e -> {
|
||||
if (e.getLeft().getFlowId().equals("flow-concurrency-cancel")) {
|
||||
if (e.getLeft().getState().getCurrent() == State.Type.SUCCESS) {
|
||||
newSuccessLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME we should fail if we receive the cancel execution again but on Kafka it happens
|
||||
});
|
||||
Execution execution3 = runnerUtils.runOne(MAIN_TENANT, "io.kestra.tests", "flow-concurrency-subflow");
|
||||
assertThat(execution3.getState().getCurrent()).isEqualTo(Type.SUCCESS);
|
||||
|
||||
// assert we have two successful subflow
|
||||
assertTrue(newSuccessLatch.await(1, TimeUnit.MINUTES));
|
||||
secondReceive.blockLast();
|
||||
}
|
||||
|
||||
private URI storageUpload() throws URISyntaxException, IOException {
|
||||
File tempFile = File.createTempFile("file", ".txt");
|
||||
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
package io.kestra.core.runners;
|
||||
|
||||
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
||||
import io.kestra.core.models.executions.Execution;
|
||||
import io.kestra.core.models.flows.DependsOn;
|
||||
import io.kestra.core.models.flows.Flow;
|
||||
import io.kestra.core.models.flows.Type;
|
||||
import io.kestra.core.models.flows.input.BoolInput;
|
||||
import io.kestra.core.models.property.Property;
|
||||
import io.kestra.core.models.property.PropertyContext;
|
||||
import io.kestra.core.models.tasks.Task;
|
||||
import io.kestra.core.models.triggers.AbstractTrigger;
|
||||
import io.kestra.core.runners.pebble.functions.SecretFunction;
|
||||
import io.kestra.core.utils.IdUtils;
|
||||
import io.micronaut.context.ApplicationContext;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -112,4 +123,25 @@ class RunVariablesTest {
|
||||
assertThat(kestra.get("environment")).isEqualTo("test");
|
||||
assertThat(kestra.get("url")).isEqualTo("http://localhost:8080");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonResolvableDynamicInputsShouldBeSkipped() throws IllegalVariableEvaluationException {
|
||||
Map<String, Object> variables = new RunVariables.DefaultBuilder()
|
||||
.withFlow(Flow
|
||||
.builder()
|
||||
.namespace("a.b")
|
||||
.id("c")
|
||||
.inputs(List.of(
|
||||
BoolInput.builder().id("a").type(Type.BOOL).defaults(Property.ofValue(true)).build(),
|
||||
BoolInput.builder().id("b").type(Type.BOOL).dependsOn(new DependsOn(List.of("a"), null)).defaults(Property.ofExpression("{{inputs.a == true}}")).build()
|
||||
))
|
||||
.build()
|
||||
)
|
||||
.withExecution(Execution.builder().id(IdUtils.create()).build())
|
||||
.build(new RunContextLogger(), PropertyContext.create(new VariableRenderer(Mockito.mock(ApplicationContext.class), Mockito.mock(VariableRenderer.VariableConfiguration.class), Collections.emptyList())));
|
||||
|
||||
Assertions.assertEquals(Map.of(
|
||||
"a", true
|
||||
), variables.get("inputs"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package io.kestra.core.serializers;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junitpioneer.jupiter.DefaultTimeZone;
|
||||
import org.junitpioneer.jupiter.RetryingTest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
@@ -86,6 +87,36 @@ class JacksonMapperTest {
|
||||
assertThat(deserialize.getZonedDateTime().toEpochSecond()).isEqualTo(original.getZonedDateTime().toEpochSecond());
|
||||
assertThat(deserialize.getZonedDateTime().getOffset()).isEqualTo(original.getZonedDateTime().getOffset());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldComputeDiffGivenCreatedObject() {
|
||||
Pair<JsonNode, JsonNode> value = JacksonMapper.getBiDirectionalDiffs(null, new DummyObject("value"));
|
||||
// patch
|
||||
assertThat(value.getLeft().toString()).isEqualTo("[{\"op\":\"replace\",\"path\":\"\",\"value\":{\"value\":\"value\"}}]");
|
||||
// Revert
|
||||
assertThat(value.getRight().toString()).isEqualTo("[{\"op\":\"replace\",\"path\":\"\",\"value\":null}]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldComputeDiffGivenUpdatedObject() {
|
||||
Pair<JsonNode, JsonNode> value = JacksonMapper.getBiDirectionalDiffs(new DummyObject("before"), new DummyObject("after"));
|
||||
// patch
|
||||
assertThat(value.getLeft().toString()).isEqualTo("[{\"op\":\"replace\",\"path\":\"/value\",\"value\":\"after\"}]");
|
||||
// Revert
|
||||
assertThat(value.getRight().toString()).isEqualTo("[{\"op\":\"replace\",\"path\":\"/value\",\"value\":\"before\"}]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldComputeDiffGivenDeletedObject() {
|
||||
Pair<JsonNode, JsonNode> value = JacksonMapper.getBiDirectionalDiffs(new DummyObject("value"), null);
|
||||
// Patch
|
||||
assertThat(value.getLeft().toString()).isEqualTo("[{\"op\":\"replace\",\"path\":\"\",\"value\":null}]");
|
||||
// Revert
|
||||
assertThat(value.getRight().toString()).isEqualTo("[{\"op\":\"replace\",\"path\":\"\",\"value\":{\"value\":\"value\"}}]");
|
||||
}
|
||||
|
||||
private record DummyObject(String value){}
|
||||
|
||||
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
|
||||
@@ -21,9 +21,11 @@ import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.time.temporal.TemporalUnit;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -91,7 +93,7 @@ class InternalKVStoreTest {
|
||||
|
||||
String description = "myDescription";
|
||||
kv.put(TEST_KV_KEY, new KVValueAndMetadata(new KVMetadata(description, Duration.ofMinutes(5)), complexValue));
|
||||
kv.put("key-without-expiration", new KVValueAndMetadata(new KVMetadata(null, null), complexValue));
|
||||
kv.put("key-without-expiration", new KVValueAndMetadata(new KVMetadata(null, (Duration) null), complexValue));
|
||||
kv.put("expired-key", new KVValueAndMetadata(new KVMetadata(null, Duration.ofMillis(1)), complexValue));
|
||||
|
||||
List<KVEntry> list = kv.listAll();
|
||||
@@ -213,6 +215,22 @@ class InternalKVStoreTest {
|
||||
// When
|
||||
Assertions.assertThrows(ResourceExpiredException.class, () -> kv.getValue(TEST_KV_KEY));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGetKVValueAndMetadata() throws IOException {
|
||||
// Given
|
||||
final InternalKVStore kv = kv();
|
||||
KVValueAndMetadata val = new KVValueAndMetadata(new KVMetadata(null, Duration.ofMinutes(5)), complexValue);
|
||||
kv.put(TEST_KV_KEY, val);
|
||||
|
||||
// When
|
||||
Optional<KVValueAndMetadata> result = kv.findMetadataAndValue(TEST_KV_KEY);
|
||||
|
||||
// Then
|
||||
Assertions.assertEquals(val.value(), result.get().value());
|
||||
Assertions.assertEquals(val.metadata().getDescription(), result.get().metadata().getDescription());
|
||||
Assertions.assertEquals(val.metadata().getExpirationDate().truncatedTo(ChronoUnit.MILLIS), result.get().metadata().getExpirationDate().truncatedTo(ChronoUnit.MILLIS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void illegalKey() {
|
||||
|
||||
@@ -204,6 +204,10 @@ class FlowTopologyServiceTest {
|
||||
io.kestra.plugin.core.trigger.Flow.UpstreamFlow.builder().namespace("io.kestra.ee").flowId("parent").build(),
|
||||
io.kestra.plugin.core.trigger.Flow.UpstreamFlow.builder().namespace("io.kestra.others").flowId("invalid").build()
|
||||
))
|
||||
// add an always true condition to validate that it's an AND between 'flows' and 'where'
|
||||
.where(List.of(io.kestra.plugin.core.trigger.Flow.ExecutionFilter.builder()
|
||||
.filters(List.of(io.kestra.plugin.core.trigger.Flow.Filter.builder().field(io.kestra.plugin.core.trigger.Flow.Field.EXPRESSION).type(io.kestra.plugin.core.trigger.Flow.Type.IS_NOT_NULL).value("something").build()))
|
||||
.build()))
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
package io.kestra.core.topologies;
|
||||
|
||||
import io.kestra.core.exceptions.FlowProcessingException;
|
||||
import io.kestra.core.junit.annotations.KestraTest;
|
||||
import io.kestra.core.models.flows.FlowWithSource;
|
||||
import io.kestra.core.models.topologies.FlowTopology;
|
||||
import io.kestra.core.repositories.FlowRepositoryInterface;
|
||||
import io.kestra.core.repositories.FlowTopologyRepositoryInterface;
|
||||
import io.kestra.core.services.FlowService;
|
||||
import io.kestra.core.utils.IdUtils;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@KestraTest
|
||||
public class FlowTopologyTest {
|
||||
@Inject
|
||||
private FlowService flowService;
|
||||
@Inject
|
||||
private FlowTopologyService flowTopologyService;
|
||||
@Inject
|
||||
private FlowTopologyRepositoryInterface flowTopologyRepository;
|
||||
|
||||
@Test
|
||||
void should_findDependencies_simpleCase() throws FlowProcessingException {
|
||||
// Given
|
||||
var tenantId = randomTenantId();
|
||||
var child = flowService.importFlow(tenantId,
|
||||
"""
|
||||
id: child
|
||||
namespace: io.kestra.unittest
|
||||
tasks:
|
||||
- id: download
|
||||
type: io.kestra.plugin.core.http.Download
|
||||
""");
|
||||
var parent = flowService.importFlow(tenantId, """
|
||||
id: parent
|
||||
namespace: io.kestra.unittest
|
||||
tasks:
|
||||
- id: subflow
|
||||
type: io.kestra.core.tasks.flows.Flow
|
||||
flowId: child
|
||||
namespace: io.kestra.unittest
|
||||
""");
|
||||
var unrelatedFlow = flowService.importFlow(tenantId, """
|
||||
id: unrelated_flow
|
||||
namespace: io.kestra.unittest
|
||||
tasks:
|
||||
- id: download
|
||||
type: io.kestra.plugin.core.http.Download
|
||||
""");
|
||||
|
||||
// When
|
||||
computeAndSaveTopologies(List.of(child, parent, unrelatedFlow));
|
||||
System.out.println();
|
||||
flowTopologyRepository.findAll(tenantId).forEach(topology -> {
|
||||
System.out.println(FlowTopologyTestData.of(topology));
|
||||
});
|
||||
|
||||
var dependencies = flowService.findDependencies(tenantId, "io.kestra.unittest", parent.getId(), false, true);
|
||||
flowTopologyRepository.findAll(tenantId).forEach(topology -> {
|
||||
System.out.println(FlowTopologyTestData.of(topology));
|
||||
});
|
||||
|
||||
// Then
|
||||
assertThat(dependencies.map(FlowTopologyTestData::of))
|
||||
.containsExactlyInAnyOrder(
|
||||
new FlowTopologyTestData(parent, child)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_findDependencies_subchildAndSuperParent() throws FlowProcessingException {
|
||||
// Given
|
||||
var tenantId = randomTenantId();
|
||||
var subChild = flowService.importFlow(tenantId,
|
||||
"""
|
||||
id: sub_child
|
||||
namespace: io.kestra.unittest
|
||||
tasks:
|
||||
- id: download
|
||||
type: io.kestra.plugin.core.http.Download
|
||||
""");
|
||||
var child = flowService.importFlow(tenantId,
|
||||
"""
|
||||
id: child
|
||||
namespace: io.kestra.unittest
|
||||
tasks:
|
||||
- id: subflow
|
||||
type: io.kestra.core.tasks.flows.Flow
|
||||
flowId: sub_child
|
||||
namespace: io.kestra.unittest
|
||||
""");
|
||||
var superParent = flowService.importFlow(tenantId, """
|
||||
id: super_parent
|
||||
namespace: io.kestra.unittest
|
||||
tasks:
|
||||
- id: subflow
|
||||
type: io.kestra.core.tasks.flows.Flow
|
||||
flowId: parent
|
||||
namespace: io.kestra.unittest
|
||||
""");
|
||||
var parent = flowService.importFlow(tenantId, """
|
||||
id: parent
|
||||
namespace: io.kestra.unittest
|
||||
tasks:
|
||||
- id: subflow
|
||||
type: io.kestra.core.tasks.flows.Flow
|
||||
flowId: child
|
||||
namespace: io.kestra.unittest
|
||||
""");
|
||||
var unrelatedFlow = flowService.importFlow(tenantId, """
|
||||
id: unrelated_flow
|
||||
namespace: io.kestra.unittest
|
||||
tasks:
|
||||
- id: download
|
||||
type: io.kestra.plugin.core.http.Download
|
||||
""");
|
||||
|
||||
// When
|
||||
computeAndSaveTopologies(List.of(subChild, child, superParent, parent, unrelatedFlow));
|
||||
System.out.println();
|
||||
flowTopologyRepository.findAll(tenantId).forEach(topology -> {
|
||||
System.out.println(FlowTopologyTestData.of(topology));
|
||||
});
|
||||
System.out.println();
|
||||
|
||||
var dependencies = flowService.findDependencies(tenantId, "io.kestra.unittest", parent.getId(), false, true);
|
||||
flowTopologyRepository.findAll(tenantId).forEach(topology -> {
|
||||
System.out.println(FlowTopologyTestData.of(topology));
|
||||
});
|
||||
|
||||
// Then
|
||||
assertThat(dependencies.map(FlowTopologyTestData::of))
|
||||
.containsExactlyInAnyOrder(
|
||||
new FlowTopologyTestData(superParent, parent),
|
||||
new FlowTopologyTestData(parent, child),
|
||||
new FlowTopologyTestData(child, subChild)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_findDependencies_cyclicTriggers() throws FlowProcessingException {
|
||||
// Given
|
||||
var tenantId = randomTenantId();
|
||||
var triggeredFlowOne = flowService.importFlow(tenantId,
|
||||
"""
|
||||
id: triggered_flow_one
|
||||
namespace: io.kestra.unittest
|
||||
tasks:
|
||||
- id: download
|
||||
type: io.kestra.plugin.core.http.Download
|
||||
triggers:
|
||||
- id: listen
|
||||
type: io.kestra.plugin.core.trigger.Flow
|
||||
conditions:
|
||||
- type: io.kestra.plugin.core.condition.ExecutionStatus
|
||||
in:
|
||||
- FAILED
|
||||
""");
|
||||
var triggeredFlowTwo = flowService.importFlow(tenantId, """
|
||||
id: triggered_flow_two
|
||||
namespace: io.kestra.unittest
|
||||
tasks:
|
||||
- id: download
|
||||
type: io.kestra.plugin.core.http.Download
|
||||
triggers:
|
||||
- id: listen
|
||||
type: io.kestra.plugin.core.trigger.Flow
|
||||
conditions:
|
||||
- type: io.kestra.plugin.core.condition.ExecutionStatus
|
||||
in:
|
||||
- FAILED
|
||||
""");
|
||||
|
||||
// When
|
||||
computeAndSaveTopologies(List.of(triggeredFlowOne, triggeredFlowTwo));
|
||||
|
||||
flowTopologyRepository.findAll(tenantId).forEach(topology -> {
|
||||
System.out.println(FlowTopologyTestData.of(topology));
|
||||
});
|
||||
|
||||
var dependencies = flowService.findDependencies(tenantId, "io.kestra.unittest", triggeredFlowTwo.getId(), false, true).toList();
|
||||
|
||||
flowTopologyRepository.findAll(tenantId).forEach(topology -> {
|
||||
System.out.println(FlowTopologyTestData.of(topology));
|
||||
});
|
||||
|
||||
// Then
|
||||
assertThat(dependencies.stream().map(FlowTopologyTestData::of))
|
||||
.containsExactlyInAnyOrder(
|
||||
new FlowTopologyTestData(triggeredFlowTwo, triggeredFlowOne),
|
||||
new FlowTopologyTestData(triggeredFlowOne, triggeredFlowTwo)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void flowTriggerWithTargetFlow() throws FlowProcessingException {
|
||||
// Given
|
||||
var tenantId = randomTenantId();
|
||||
var parent = flowService.importFlow(tenantId,
|
||||
"""
|
||||
id: parent
|
||||
namespace: io.kestra.unittest
|
||||
inputs:
|
||||
- id: a
|
||||
type: BOOL
|
||||
defaults: true
|
||||
|
||||
- id: b
|
||||
type: BOOL
|
||||
defaults: "{{ inputs.a == true }}"
|
||||
dependsOn:
|
||||
inputs:
|
||||
- a
|
||||
tasks:
|
||||
- id: helloA
|
||||
type: io.kestra.plugin.core.log.Log
|
||||
message: Hello A
|
||||
""");
|
||||
var child = flowService.importFlow(tenantId, """
|
||||
id: child
|
||||
namespace: io.kestra.unittest
|
||||
tasks:
|
||||
- id: helloB
|
||||
type: io.kestra.plugin.core.log.Log
|
||||
message: Hello B
|
||||
triggers:
|
||||
- id: release
|
||||
type: io.kestra.plugin.core.trigger.Flow
|
||||
states:
|
||||
- SUCCESS
|
||||
preconditions:
|
||||
id: flows
|
||||
flows:
|
||||
- namespace: io.kestra.unittest
|
||||
flowId: parent
|
||||
""");
|
||||
var unrelatedFlow = flowService.importFlow(tenantId, """
|
||||
id: unrelated_flow
|
||||
namespace: io.kestra.unittest
|
||||
tasks:
|
||||
- id: download
|
||||
type: io.kestra.plugin.core.http.Download
|
||||
""");
|
||||
|
||||
// When
|
||||
computeAndSaveTopologies(List.of(child, parent, unrelatedFlow));
|
||||
System.out.println();
|
||||
flowTopologyRepository.findAll(tenantId).forEach(topology -> {
|
||||
System.out.println(FlowTopologyTestData.of(topology));
|
||||
});
|
||||
|
||||
var dependencies = flowService.findDependencies(tenantId, "io.kestra.unittest", parent.getId(), false, true);
|
||||
flowTopologyRepository.findAll(tenantId).forEach(topology -> {
|
||||
System.out.println(FlowTopologyTestData.of(topology));
|
||||
});
|
||||
|
||||
// Then
|
||||
assertThat(dependencies.map(FlowTopologyTestData::of))
|
||||
.containsExactlyInAnyOrder(
|
||||
new FlowTopologyTestData(parent, child)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* this function mimics the production behaviour
|
||||
*/
|
||||
private void computeAndSaveTopologies(List<@NotNull FlowWithSource> flows) {
|
||||
flows.forEach(flow ->
|
||||
flowTopologyService
|
||||
.topology(
|
||||
flow,
|
||||
flows
|
||||
).distinct()
|
||||
.forEach(topology -> flowTopologyRepository.save(topology))
|
||||
);
|
||||
}
|
||||
|
||||
private static String randomTenantId() {
|
||||
return FlowTopologyTest.class + IdUtils.create();
|
||||
}
|
||||
|
||||
|
||||
record FlowTopologyTestData(String sourceUid, String destinationUid) {
|
||||
public FlowTopologyTestData(FlowWithSource parent, FlowWithSource child) {
|
||||
this(parent.uidWithoutRevision(), child.uidWithoutRevision());
|
||||
}
|
||||
|
||||
public static FlowTopologyTestData of(FlowTopology flowTopology) {
|
||||
return new FlowTopologyTestData(flowTopology.getSource().getUid(), flowTopology.getDestination().getUid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return sourceUid + " -> " + destinationUid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class MapUtilsTest {
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -208,10 +207,13 @@ class MapUtilsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowIfNestedMapContainsMultipleEntries() {
|
||||
var exception = assertThrows(IllegalArgumentException.class,
|
||||
() -> MapUtils.nestedToFlattenMap(Map.of("k1", Map.of("k2", Map.of("k3", "v1"), "k4", "v2")))
|
||||
);
|
||||
assertThat(exception.getMessage()).isEqualTo("You cannot flatten a map with an entry that is a map of more than one element, conflicting key: k1");
|
||||
void shouldFlattenANestedMapWithDuplicateKeys() {
|
||||
Map<String, Object> results = MapUtils.nestedToFlattenMap(Map.of("k1", Map.of("k2", Map.of("k3", "v1"), "k4", "v2")));
|
||||
|
||||
assertThat(results).hasSize(2);
|
||||
assertThat(results).containsAllEntriesOf(Map.of(
|
||||
"k1.k2.k3", "v1",
|
||||
"k1.k4", "v2"
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ class ExitTest {
|
||||
@ExecuteFlow("flows/valids/exit.yaml")
|
||||
void shouldExitTheExecution(Execution execution) {
|
||||
assertThat(execution.getState().getCurrent()).isEqualTo(State.Type.WARNING);
|
||||
assertThat(execution.getTaskRunList().size()).isEqualTo(2);
|
||||
assertThat(execution.getTaskRunList()).hasSize(2);
|
||||
assertThat(execution.getTaskRunList().getFirst().getState().getCurrent()).isEqualTo(State.Type.WARNING);
|
||||
}
|
||||
|
||||
@@ -68,4 +68,14 @@ class ExitTest {
|
||||
assertThat(killedExecution.get().getTaskRunList().get(1).getState().getCurrent()).isEqualTo(State.Type.KILLED);
|
||||
receive.blockLast();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExecuteFlow("flows/valids/exit-nested.yaml")
|
||||
void shouldExitAndFailNestedIf(Execution execution) {
|
||||
assertThat(execution.getState().getCurrent()).isEqualTo(State.Type.FAILED);
|
||||
assertThat(execution.getTaskRunList()).hasSize(4);
|
||||
assertThat(execution.findTaskRunsByTaskId("if_some_bool").getFirst().getState().getCurrent()).isEqualTo(State.Type.FAILED);
|
||||
assertThat(execution.findTaskRunsByTaskId("nested_bool_check").getFirst().getState().getCurrent()).isEqualTo(State.Type.FAILED);
|
||||
assertThat(execution.findTaskRunsByTaskId("nested_was_false").getFirst().getState().getCurrent()).isEqualTo(State.Type.FAILED);
|
||||
}
|
||||
}
|
||||
@@ -126,7 +126,7 @@ public class PurgeKVTest {
|
||||
KVStore kvStore2 = runContext.namespaceKv(namespace2);
|
||||
kvStore2.put(KEY_EXPIRED, new KVValueAndMetadata(new KVMetadata("unused", Duration.ofMillis(1L)), "unused"));
|
||||
kvStore2.put(KEY, new KVValueAndMetadata(new KVMetadata("unused", Duration.ofMinutes(1L)), "unused"));
|
||||
kvStore2.put(KEY2_NEVER_EXPIRING, new KVValueAndMetadata(new KVMetadata("unused", null), "unused"));
|
||||
kvStore2.put(KEY2_NEVER_EXPIRING, new KVValueAndMetadata(new KVMetadata("unused", (Duration) null), "unused"));
|
||||
kvStore2.put(KEY3_NEVER_EXPIRING, new KVValueAndMetadata(null, "unused"));
|
||||
|
||||
PurgeKV purgeKV = PurgeKV.builder()
|
||||
@@ -152,7 +152,7 @@ public class PurgeKVTest {
|
||||
KVStore kvStore1 = runContext.namespaceKv(namespace);
|
||||
kvStore1.put(KEY_EXPIRED, new KVValueAndMetadata(new KVMetadata("unused", Duration.ofMillis(1L)), "unused"));
|
||||
kvStore1.put(KEY, new KVValueAndMetadata(new KVMetadata("unused", Duration.ofMinutes(1L)), "unused"));
|
||||
kvStore1.put(KEY2_NEVER_EXPIRING, new KVValueAndMetadata(new KVMetadata("unused", null), "unused"));
|
||||
kvStore1.put(KEY2_NEVER_EXPIRING, new KVValueAndMetadata(new KVMetadata("unused",(Duration) null), "unused"));
|
||||
kvStore1.put(KEY3_NEVER_EXPIRING, new KVValueAndMetadata(null, "unused"));
|
||||
|
||||
PurgeKV purgeKV = PurgeKV.builder()
|
||||
|
||||
36
core/src/test/resources/flows/valids/exit-nested.yaml
Normal file
36
core/src/test/resources/flows/valids/exit-nested.yaml
Normal file
@@ -0,0 +1,36 @@
|
||||
id: exit-nested
|
||||
namespace: io.kestra.tests
|
||||
|
||||
inputs:
|
||||
- id: someBool
|
||||
type: BOOL
|
||||
defaults: true
|
||||
|
||||
- id: secondBool
|
||||
type: BOOL
|
||||
defaults: false
|
||||
|
||||
tasks:
|
||||
- id: if_some_bool
|
||||
type: io.kestra.plugin.core.flow.If
|
||||
condition: "{{ inputs.someBool }}"
|
||||
then:
|
||||
- id: was_true
|
||||
type: io.kestra.plugin.core.log.Log
|
||||
message: The value was true
|
||||
|
||||
- id: nested_bool_check
|
||||
type: io.kestra.plugin.core.flow.If
|
||||
condition: "{{ inputs.secondBool }}"
|
||||
then:
|
||||
- id: was_also_true
|
||||
type: io.kestra.plugin.core.log.Log
|
||||
message: Was also true
|
||||
else:
|
||||
- id: nested_was_false
|
||||
type: io.kestra.plugin.core.execution.Exit
|
||||
state: FAILED
|
||||
else:
|
||||
- id: was_false
|
||||
type: io.kestra.plugin.core.execution.Exit
|
||||
state: FAILED
|
||||
@@ -0,0 +1,8 @@
|
||||
id: flow-concurrency-subflow
|
||||
namespace: io.kestra.tests
|
||||
|
||||
tasks:
|
||||
- id: subflow
|
||||
type: io.kestra.plugin.core.flow.Subflow
|
||||
namespace: io.kestra.tests
|
||||
flowId: flow-concurrency-cancel
|
||||
@@ -1171,7 +1171,7 @@ public class ExecutorService {
|
||||
}
|
||||
}
|
||||
|
||||
return taskRuns.size() > execution.getTaskRunList().size() ? execution.withTaskRunList(taskRuns) : null;
|
||||
return taskRuns.size() > ListUtils.emptyOnNull(execution.getTaskRunList()).size() ? execution.withTaskRunList(taskRuns) : null;
|
||||
}
|
||||
|
||||
public boolean canBePurged(final Executor executor) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version=1.0.0-SNAPSHOT
|
||||
version=1.0.2
|
||||
|
||||
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.parallel=true
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- We must truncate the table as in 0.24 there was a bug that lead to records not purged in this table
|
||||
truncate table execution_running;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- We must truncate the table as in 0.24 there was a bug that lead to records not purged in this table
|
||||
truncate table execution_running;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- We must truncate the table as in 0.24 there was a bug that lead to records not purged in this table
|
||||
truncate table execution_running;
|
||||
@@ -150,13 +150,8 @@ public abstract class AbstractJdbcDashboardRepository extends AbstractJdbcReposi
|
||||
fields.put(field("source_code"), source);
|
||||
|
||||
this.jdbcRepository.persist(dashboard, fields);
|
||||
|
||||
if (previousDashboard == null) {
|
||||
eventPublisher.publishEvent(new CrudEvent<>(dashboard, CrudEventType.CREATE));
|
||||
} else {
|
||||
eventPublisher.publishEvent(new CrudEvent<>(dashboard, previousDashboard, CrudEventType.UPDATE));
|
||||
}
|
||||
|
||||
this.eventPublisher.publishEvent(CrudEvent.of(previousDashboard, dashboard));
|
||||
|
||||
return dashboard;
|
||||
}
|
||||
|
||||
@@ -174,8 +169,7 @@ public abstract class AbstractJdbcDashboardRepository extends AbstractJdbcReposi
|
||||
fields.put(field("source_code"), deleted.getSourceCode());
|
||||
|
||||
this.jdbcRepository.persist(deleted, fields);
|
||||
|
||||
eventPublisher.publishEvent(new CrudEvent<>(dashboard.get(), CrudEventType.DELETE));
|
||||
this.eventPublisher.publishEvent(CrudEvent.delete(dashboard.get()));
|
||||
|
||||
return deleted;
|
||||
}
|
||||
|
||||
@@ -969,14 +969,16 @@ public abstract class AbstractJdbcExecutionRepository extends AbstractJdbcReposi
|
||||
|
||||
executionQueue().emit(deleted);
|
||||
|
||||
eventPublisher.publishEvent(new CrudEvent<>(deleted, CrudEventType.DELETE));
|
||||
eventPublisher.publishEvent(CrudEvent.delete(deleted));
|
||||
|
||||
return deleted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer purge(Execution execution) {
|
||||
return this.jdbcRepository.delete(execution);
|
||||
int delete = this.jdbcRepository.delete(execution);
|
||||
eventPublisher.publishEvent(CrudEvent.delete(execution));
|
||||
return delete;
|
||||
}
|
||||
|
||||
public Executor lock(String executionId, Function<Pair<Execution, ExecutorState>, Pair<Executor, ExecutorState>> function) {
|
||||
|
||||
@@ -700,12 +700,7 @@ public abstract class AbstractJdbcFlowRepository extends AbstractJdbcRepository
|
||||
this.jdbcRepository.persist(flow, fields);
|
||||
|
||||
flowQueue.emit(flow);
|
||||
|
||||
if (nullOrExisting != null) {
|
||||
eventPublisher.publishEvent(new CrudEvent<>(flow, nullOrExisting, crudEventType));
|
||||
} else {
|
||||
eventPublisher.publishEvent(new CrudEvent<>(flow, crudEventType));
|
||||
}
|
||||
eventPublisher.publishEvent(new CrudEvent<>(flow, nullOrExisting, crudEventType));
|
||||
|
||||
return flowWithSource.toBuilder().revision(revision).build();
|
||||
}
|
||||
@@ -735,8 +730,7 @@ public abstract class AbstractJdbcFlowRepository extends AbstractJdbcRepository
|
||||
this.jdbcRepository.persist(deleted, fields);
|
||||
|
||||
flowQueue.emit(deleted);
|
||||
|
||||
eventPublisher.publishEvent(new CrudEvent<>(flow, CrudEventType.DELETE));
|
||||
eventPublisher.publishEvent(CrudEvent.delete(flow));
|
||||
|
||||
return deleted;
|
||||
}
|
||||
|
||||
@@ -67,8 +67,7 @@ public abstract class AbstractJdbcSettingRepository extends AbstractJdbcReposito
|
||||
public Setting save(Setting setting) {
|
||||
Map<Field<Object>, Object> fields = this.jdbcRepository.persistFields(setting);
|
||||
this.jdbcRepository.persist(setting, fields);
|
||||
|
||||
eventPublisher.publishEvent(new CrudEvent<>(setting, CrudEventType.UPDATE));
|
||||
this.eventPublisher.publishEvent(new CrudEvent<>(setting, CrudEventType.UPDATE));
|
||||
|
||||
return setting;
|
||||
}
|
||||
@@ -82,8 +81,7 @@ public abstract class AbstractJdbcSettingRepository extends AbstractJdbcReposito
|
||||
}
|
||||
|
||||
this.jdbcRepository.delete(setting);
|
||||
|
||||
eventPublisher.publishEvent(new CrudEvent<>(setting, CrudEventType.DELETE));
|
||||
this.eventPublisher.publishEvent(CrudEvent.delete(setting));
|
||||
|
||||
return setting;
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ public abstract class AbstractJdbcTemplateRepository extends AbstractJdbcReposit
|
||||
|
||||
try {
|
||||
templateQueue.emit(template);
|
||||
eventPublisher.publishEvent(new CrudEvent<>(template, CrudEventType.CREATE));
|
||||
eventPublisher.publishEvent(CrudEvent.create(template));
|
||||
|
||||
return template;
|
||||
} catch (QueueException e) {
|
||||
@@ -217,7 +217,7 @@ public abstract class AbstractJdbcTemplateRepository extends AbstractJdbcReposit
|
||||
|
||||
try {
|
||||
templateQueue.emit(deleted);
|
||||
eventPublisher.publishEvent(new CrudEvent<>(deleted, CrudEventType.DELETE));
|
||||
eventPublisher.publishEvent(CrudEvent.delete(deleted));
|
||||
} catch (QueueException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@@ -991,32 +991,28 @@ public class JdbcExecutor implements ExecutorInterface {
|
||||
}
|
||||
|
||||
ExecutionRunning executionRunning = either.getLeft();
|
||||
FlowInterface flow = flowMetaStore.findByExecution(executionRunning.getExecution()).orElseThrow();
|
||||
ExecutionRunning processed = executionRunningStorage.countThenProcess(flow, (dslContext, count) -> {
|
||||
ExecutionRunning computed = executorService.processExecutionRunning(flow, count, executionRunning);
|
||||
if (computed.getConcurrencyState() == ExecutionRunning.ConcurrencyState.RUNNING && !computed.getExecution().getState().isTerminated()) {
|
||||
executionRunningStorage.save(dslContext, computed);
|
||||
} else if (computed.getConcurrencyState() == ExecutionRunning.ConcurrencyState.QUEUED) {
|
||||
executionQueuedStorage.save(dslContext, ExecutionQueued.fromExecutionRunning(computed));
|
||||
}
|
||||
return computed;
|
||||
});
|
||||
// we need to update the execution after applying concurrency limit so we use the lock for that
|
||||
Executor executor = executionRepository.lock(executionRunning.getExecution().getId(), pair -> {
|
||||
Execution execution = pair.getLeft();
|
||||
Executor newExecutor = new Executor(execution, null);
|
||||
FlowInterface flow = flowMetaStore.findByExecution(execution).orElseThrow();
|
||||
ExecutionRunning processed = executionRunningStorage.countThenProcess(flow, (dslContext, count) -> {
|
||||
ExecutionRunning computed = executorService.processExecutionRunning(flow, count, executionRunning.withExecution(execution)); // be sure that the execution running contains the latest value of the execution
|
||||
if (computed.getConcurrencyState() == ExecutionRunning.ConcurrencyState.RUNNING && !computed.getExecution().getState().isTerminated()) {
|
||||
executionRunningStorage.save(dslContext, computed);
|
||||
} else if (computed.getConcurrencyState() == ExecutionRunning.ConcurrencyState.QUEUED) {
|
||||
executionQueuedStorage.save(dslContext, ExecutionQueued.fromExecutionRunning(computed));
|
||||
}
|
||||
return computed;
|
||||
});
|
||||
|
||||
try {
|
||||
executionQueue.emit(processed.getExecution());
|
||||
|
||||
// process flow triggers to allow listening on QUEUED and RUNNING state for concurrency limit
|
||||
flowTriggerService.computeExecutionsFromFlowTriggers(processed.getExecution(), allFlows, Optional.of(multipleConditionStorage))
|
||||
.forEach(throwConsumer(executionFromFlowTrigger -> this.executionQueue.emit(executionFromFlowTrigger)));
|
||||
} catch (QueueException e) {
|
||||
try {
|
||||
this.executionQueue.emit(
|
||||
processed.getExecution().failedExecutionFromExecutor(e).getExecution().withState(State.Type.FAILED)
|
||||
return Pair.of(
|
||||
newExecutor.withExecution(processed.getExecution(), "handleExecutionRunning"),
|
||||
pair.getRight()
|
||||
);
|
||||
} catch (QueueException ex) {
|
||||
log.error("Unable to emit the execution {}", processed.getExecution().getId(), ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
toExecution(executor);
|
||||
}
|
||||
|
||||
private Executor killingOrAfterKillState(final String executionId, Optional<State.Type> afterKillState) {
|
||||
|
||||
@@ -301,6 +301,8 @@ public abstract class AbstractScheduler implements Scheduler {
|
||||
// Initialized local trigger state,
|
||||
// and if some flows were created outside the box, for example from the CLI,
|
||||
// then we may have some triggers that are not created yet.
|
||||
/* FIXME: There is a race between Kafka stream consumption & initializedTriggers: we can override a trigger update coming from a stream consumption with an old one because stream consumption is not waiting for trigger initialization
|
||||
* Example: we see a SUCCESS execution so we reset the trigger's executionId but then the initializedTriggers resubmits an old trigger state for some reasons (evaluationDate for eg.) */
|
||||
private void initializedTriggers(List<FlowWithSource> flows) {
|
||||
record FlowAndTrigger(FlowWithSource flow, AbstractTrigger trigger) {
|
||||
@Override
|
||||
@@ -371,10 +373,13 @@ public abstract class AbstractScheduler implements Scheduler {
|
||||
|
||||
this.triggerState.update(lastUpdate);
|
||||
}
|
||||
} else if (recoverMissedSchedules == RecoverMissedSchedules.NONE) {
|
||||
lastUpdate = trigger.get().toBuilder().nextExecutionDate(schedule.nextEvaluationDate()).build();
|
||||
} else {
|
||||
ZonedDateTime nextEvaluationDate = schedule.nextEvaluationDate();
|
||||
if (recoverMissedSchedules == RecoverMissedSchedules.NONE && !Objects.equals(trigger.get().getNextExecutionDate(), nextEvaluationDate)) {
|
||||
lastUpdate = trigger.get().toBuilder().nextExecutionDate(nextEvaluationDate).build();
|
||||
|
||||
this.triggerState.update(lastUpdate);
|
||||
this.triggerState.update(lastUpdate);
|
||||
}
|
||||
}
|
||||
// Used for schedulableNextDate
|
||||
FlowWithWorkerTrigger flowWithWorkerTrigger = FlowWithWorkerTrigger.builder()
|
||||
|
||||
@@ -30,6 +30,7 @@ import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
@@ -88,6 +89,7 @@ public class SchedulerTriggerChangeTest extends AbstractSchedulerTest {
|
||||
return FlowWithSource.of(flow, flow.getSource());
|
||||
}
|
||||
|
||||
@Disabled("Way too flaky on the CI")
|
||||
@Test
|
||||
void run() throws Exception {
|
||||
CountDownLatch executionQueueCount = new CountDownLatch(1);
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.kestra.core.junit.extensions;
|
||||
|
||||
import io.kestra.core.junit.annotations.KestraTest;
|
||||
import io.kestra.core.runners.TestRunner;
|
||||
import io.kestra.core.utils.TestsUtils;
|
||||
import io.micronaut.test.annotation.MicronautTestValue;
|
||||
import io.micronaut.test.extensions.junit5.MicronautJunit5Extension;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
@@ -55,4 +56,11 @@ public class KestraTestExtension extends MicronautJunit5Extension {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTestExecution(ExtensionContext context) throws Exception {
|
||||
super.afterTestExecution(context);
|
||||
|
||||
TestsUtils.queueConsumersCleanup();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,6 +663,14 @@ public abstract class StorageTestSuite {
|
||||
put(tenantId, prefix);
|
||||
}
|
||||
|
||||
@Test
|
||||
void put_PathWithTenantStringInIt() throws Exception {
|
||||
String tenantId = IdUtils.create();
|
||||
String prefix = tenantId + "/" + IdUtils.create();
|
||||
|
||||
put(tenantId, prefix);
|
||||
}
|
||||
|
||||
@Test
|
||||
void putFromAnotherFile() throws Exception {
|
||||
String prefix = IdUtils.create();
|
||||
@@ -982,6 +990,14 @@ public abstract class StorageTestSuite {
|
||||
deleteByPrefix(prefix, tenantId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteByPrefix_PathWithTenantStringInIt() throws Exception {
|
||||
String tenantId = IdUtils.create();
|
||||
String prefix = tenantId + "/" + IdUtils.create();
|
||||
|
||||
deleteByPrefix(prefix, tenantId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteByPrefixNotFound() throws URISyntaxException, IOException {
|
||||
String prefix = IdUtils.create();
|
||||
|
||||
@@ -41,8 +41,15 @@ import java.util.stream.Collectors;
|
||||
import static io.kestra.core.tenant.TenantService.MAIN_TENANT;
|
||||
|
||||
abstract public class TestsUtils {
|
||||
private static final ThreadLocal<List<Runnable>> queueConsumersCancellations = ThreadLocal.withInitial(ArrayList::new);
|
||||
|
||||
private static final ObjectMapper mapper = JacksonMapper.ofYaml();
|
||||
|
||||
public static void queueConsumersCleanup() {
|
||||
queueConsumersCancellations.get().forEach(Runnable::run);
|
||||
queueConsumersCancellations.get().clear();
|
||||
}
|
||||
|
||||
public static <T> T map(String path, Class<T> cls) throws IOException {
|
||||
URL resource = TestsUtils.class.getClassLoader().getResource(path);
|
||||
assert resource != null;
|
||||
@@ -214,6 +221,7 @@ abstract public class TestsUtils {
|
||||
}
|
||||
};
|
||||
Runnable receiveCancellation = queueType == null ? queue.receive(consumerGroup, eitherConsumer, false) : queue.receive(consumerGroup, queueType, eitherConsumer, false);
|
||||
queueConsumersCancellations.get().add(receiveCancellation);
|
||||
|
||||
return Flux.<T>create(sink -> {
|
||||
DeserializationException exception = exceptionRef.get();
|
||||
|
||||
666
ui/package-lock.json
generated
666
ui/package-lock.json
generated
@@ -10,7 +10,7 @@
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@js-joda/core": "^5.6.5",
|
||||
"@kestra-io/ui-libs": "^0.0.245",
|
||||
"@kestra-io/ui-libs": "^0.0.250",
|
||||
"@vue-flow/background": "^1.3.2",
|
||||
"@vue-flow/controls": "^1.1.2",
|
||||
"@vue-flow/core": "^1.46.2",
|
||||
@@ -95,9 +95,8 @@
|
||||
"@vueuse/router": "^13.9.0",
|
||||
"change-case": "5.4.4",
|
||||
"cross-env": "^10.0.0",
|
||||
"decompress": "^4.2.1",
|
||||
"eslint": "^9.34.0",
|
||||
"eslint-plugin-storybook": "^9.1.4",
|
||||
"eslint": "^9.35.0",
|
||||
"eslint-plugin-storybook": "^9.1.5",
|
||||
"eslint-plugin-vue": "^9.33.0",
|
||||
"globals": "^16.3.0",
|
||||
"husky": "^9.1.7",
|
||||
@@ -1618,9 +1617,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint-community/eslint-utils": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
|
||||
"integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
|
||||
"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": {
|
||||
@@ -1793,9 +1792,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz",
|
||||
"integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==",
|
||||
"version": "9.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz",
|
||||
"integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -3220,9 +3219,9 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@kestra-io/ui-libs": {
|
||||
"version": "0.0.245",
|
||||
"resolved": "https://registry.npmjs.org/@kestra-io/ui-libs/-/ui-libs-0.0.245.tgz",
|
||||
"integrity": "sha512-nJOq5gG5SxbsGtX7LuWR32YNq5eiCaObhPNW5rQ7071dJAc11aB7qAdycOPlG1uVMj9AUhlsMaBtjdLsKb4IMw==",
|
||||
"version": "0.0.250",
|
||||
"resolved": "https://registry.npmjs.org/@kestra-io/ui-libs/-/ui-libs-0.0.250.tgz",
|
||||
"integrity": "sha512-Y0ANjGn91f+3G6ZeH0niorf0ZCNe/BPWfur+yHni4AKHbyNUZjrE8UN9ETvOlYe5c2qQSyQdM9yK/LdG1Thtzw==",
|
||||
"dependencies": {
|
||||
"@nuxtjs/mdc": "^0.16.1",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
@@ -7748,26 +7747,10 @@
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/available-typed-arrays": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||
"integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"possible-typed-array-names": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
|
||||
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
|
||||
"version": "1.12.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
|
||||
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
@@ -8045,57 +8028,6 @@
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz",
|
||||
"integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readable-stream": "^2.3.5",
|
||||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bl/node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bl/node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bl/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bl/node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/boolbase": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||
@@ -8211,41 +8143,6 @@
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-alloc": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
|
||||
"integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-alloc-unsafe": "^1.1.0",
|
||||
"buffer-fill": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-alloc-unsafe": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
|
||||
"integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/buffer-crc32": {
|
||||
"version": "0.2.13",
|
||||
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
|
||||
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-fill": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
|
||||
"integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
@@ -8930,13 +8827,6 @@
|
||||
"url": "https://opencollective.com/core-js"
|
||||
}
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cose-base": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
|
||||
@@ -9715,109 +9605,6 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/decompress": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz",
|
||||
"integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"decompress-tar": "^4.0.0",
|
||||
"decompress-tarbz2": "^4.0.0",
|
||||
"decompress-targz": "^4.0.0",
|
||||
"decompress-unzip": "^4.0.1",
|
||||
"graceful-fs": "^4.1.10",
|
||||
"make-dir": "^1.0.0",
|
||||
"pify": "^2.3.0",
|
||||
"strip-dirs": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/decompress-tar": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz",
|
||||
"integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"file-type": "^5.2.0",
|
||||
"is-stream": "^1.1.0",
|
||||
"tar-stream": "^1.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/decompress-tarbz2": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz",
|
||||
"integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"decompress-tar": "^4.1.0",
|
||||
"file-type": "^6.1.0",
|
||||
"is-stream": "^1.1.0",
|
||||
"seek-bzip": "^1.0.5",
|
||||
"unbzip2-stream": "^1.0.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/decompress-tarbz2/node_modules/file-type": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz",
|
||||
"integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/decompress-targz": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz",
|
||||
"integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"decompress-tar": "^4.1.1",
|
||||
"file-type": "^5.2.0",
|
||||
"is-stream": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/decompress-unzip": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz",
|
||||
"integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"file-type": "^3.8.0",
|
||||
"get-stream": "^2.2.0",
|
||||
"pify": "^2.3.0",
|
||||
"yauzl": "^2.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/decompress-unzip/node_modules/file-type": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
|
||||
"integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dedent": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz",
|
||||
@@ -10333,16 +10120,6 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
@@ -10568,19 +10345,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "9.34.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz",
|
||||
"integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==",
|
||||
"version": "9.36.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz",
|
||||
"integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
"@eslint/config-array": "^0.21.0",
|
||||
"@eslint/config-helpers": "^0.3.1",
|
||||
"@eslint/core": "^0.15.2",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "9.34.0",
|
||||
"@eslint/js": "9.36.0",
|
||||
"@eslint/plugin-kit": "^0.3.5",
|
||||
"@humanfs/node": "^0.16.6",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
@@ -10676,9 +10453,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-storybook": {
|
||||
"version": "9.1.4",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-9.1.4.tgz",
|
||||
"integrity": "sha512-IiIqGFo524PDELajyDLMtceikHpDUKBF6QlH5oJECy+xV3e0DHJkcuyokwxWveb1yg7tHfTLimCKNix2ftRETg==",
|
||||
"version": "9.1.7",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-9.1.7.tgz",
|
||||
"integrity": "sha512-Bq9VNutFGX7T0jw7luWt5eEyRFInIsE0+FSaXdayqBNW6NPaGuE+hoBhhTowvohNqEqn5DXwIkPHiI1GhONE9g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -10689,7 +10466,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=8",
|
||||
"storybook": "^9.1.4"
|
||||
"storybook": "^9.1.7"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-vue": {
|
||||
@@ -11145,16 +10922,6 @@
|
||||
"bser": "2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fd-slicer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
||||
"integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pend": "~1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.4.8",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz",
|
||||
@@ -11174,16 +10941,6 @@
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/file-type": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz",
|
||||
"integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
@@ -11370,22 +11127,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/for-each": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||
"integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-callable": "^1.2.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz",
|
||||
@@ -11437,13 +11178,6 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fs-exists-sync": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz",
|
||||
@@ -11581,20 +11315,6 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
|
||||
"integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"object-assign": "^4.0.1",
|
||||
"pinkie-promise": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/giget": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz",
|
||||
@@ -12572,19 +12292,6 @@
|
||||
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-callable": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
|
||||
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
@@ -12707,13 +12414,6 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/is-natural-number": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz",
|
||||
"integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
@@ -12779,32 +12479,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-stream": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||
"integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-typed-array": {
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
|
||||
"integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"which-typed-array": "^1.1.16"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-typedarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||
@@ -15442,29 +15116,6 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
|
||||
"integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pify": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir/node_modules/pify": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
|
||||
"integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
@@ -17812,13 +17463,6 @@
|
||||
"@napi-rs/canvas": "^0.1.77"
|
||||
}
|
||||
},
|
||||
"node_modules/pend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/perfect-debounce": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
|
||||
@@ -17857,16 +17501,6 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/pify": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pinia": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz",
|
||||
@@ -17888,29 +17522,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pinkie": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
|
||||
"integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pinkie-promise": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
|
||||
"integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pinkie": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pirates": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
||||
@@ -18064,16 +17675,6 @@
|
||||
"points-on-curve": "0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||
"integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
@@ -18256,13 +17857,6 @@
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/process-on-spawn": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz",
|
||||
@@ -19489,27 +19083,6 @@
|
||||
"integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/seek-bzip": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz",
|
||||
"integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commander": "^2.8.1"
|
||||
},
|
||||
"bin": {
|
||||
"seek-bunzip": "bin/seek-bunzip",
|
||||
"seek-table": "bin/seek-bzip-table"
|
||||
}
|
||||
},
|
||||
"node_modules/seek-bzip/node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
@@ -20105,16 +19678,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-dirs": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz",
|
||||
"integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-natural-number": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-final-newline": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
|
||||
@@ -20262,65 +19825,6 @@
|
||||
"url": "https://opencollective.com/synckit"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz",
|
||||
"integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bl": "^1.0.0",
|
||||
"buffer-alloc": "^1.2.0",
|
||||
"end-of-stream": "^1.0.0",
|
||||
"fs-constants": "^1.0.0",
|
||||
"readable-stream": "^2.3.0",
|
||||
"to-buffer": "^1.1.1",
|
||||
"xtend": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream/node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tar-stream/node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tar-stream/node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/test-exclude": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz",
|
||||
@@ -20396,13 +19900,6 @@
|
||||
"node": ">=12.22"
|
||||
}
|
||||
},
|
||||
"node_modules/through": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
@@ -20538,21 +20035,6 @@
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/to-buffer": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz",
|
||||
"integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"isarray": "^2.0.5",
|
||||
"safe-buffer": "^5.2.1",
|
||||
"typed-array-buffer": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -20845,21 +20327,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/typed-array-buffer": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
|
||||
"integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.3",
|
||||
"es-errors": "^1.3.0",
|
||||
"is-typed-array": "^1.1.14"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/typedarray-to-buffer": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
|
||||
@@ -20929,42 +20396,6 @@
|
||||
"integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unbzip2-stream": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
|
||||
"integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer": "^5.2.1",
|
||||
"through": "^2.3.8"
|
||||
}
|
||||
},
|
||||
"node_modules/unbzip2-stream/node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/unctx": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/unctx/-/unctx-2.4.1.tgz",
|
||||
@@ -21459,9 +20890,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.3.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
|
||||
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
|
||||
"version": "6.3.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
|
||||
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -22415,28 +21846,6 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/which-typed-array": {
|
||||
"version": "1.1.19",
|
||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz",
|
||||
"integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"available-typed-arrays": "^1.0.7",
|
||||
"call-bind": "^1.0.8",
|
||||
"call-bound": "^1.0.4",
|
||||
"for-each": "^0.3.5",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-tostringtag": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/why-is-node-running": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
|
||||
@@ -22689,16 +22098,6 @@
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
@@ -22789,17 +22188,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yauzl": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
||||
"integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-crc32": "~0.2.3",
|
||||
"fd-slicer": "~1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@js-joda/core": "^5.6.5",
|
||||
"@kestra-io/ui-libs": "^0.0.245",
|
||||
"@kestra-io/ui-libs": "^0.0.250",
|
||||
"@vue-flow/background": "^1.3.2",
|
||||
"@vue-flow/controls": "^1.1.2",
|
||||
"@vue-flow/core": "^1.46.2",
|
||||
@@ -109,9 +109,8 @@
|
||||
"@vueuse/router": "^13.9.0",
|
||||
"change-case": "5.4.4",
|
||||
"cross-env": "^10.0.0",
|
||||
"decompress": "^4.2.1",
|
||||
"eslint": "^9.34.0",
|
||||
"eslint-plugin-storybook": "^9.1.4",
|
||||
"eslint": "^9.35.0",
|
||||
"eslint-plugin-storybook": "^9.1.5",
|
||||
"eslint-plugin-vue": "^9.33.0",
|
||||
"globals": "^16.3.0",
|
||||
"husky": "^9.1.7",
|
||||
|
||||
@@ -45,6 +45,10 @@
|
||||
|
||||
function removeItem(yaml: string, index: number){
|
||||
flowStore.flowYaml = yaml;
|
||||
if(items.value.length <= 1 && index === 0){
|
||||
emits("update:modelValue", undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
let localItems = [...items.value]
|
||||
localItems.splice(index, 1)
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
const dashboardComponent = useTemplateRef("dashboardComponent");
|
||||
|
||||
const refreshCharts = () => {
|
||||
dashboardComponent.value!.refreshCharts();
|
||||
dashboardComponent.value?.refreshCharts?.();
|
||||
};
|
||||
|
||||
const load = async (id = "default", defaultYAML = YAML_MAIN) => {
|
||||
|
||||
@@ -75,9 +75,8 @@
|
||||
import type {Dashboard, Chart} from "../composables/useDashboards";
|
||||
import {TYPES, isKPIChart, isTableChart, getChartTitle} from "../composables/useDashboards";
|
||||
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import {useRoute} from "vue-router";
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
import {useDashboardStore} from "../../../stores/dashboard";
|
||||
const dashboardStore = useDashboardStore();
|
||||
@@ -116,13 +115,6 @@
|
||||
|
||||
const filters = ref<{ field: string; operation: string; value: string | string[] }[]>([]);
|
||||
onMounted(() => {
|
||||
const dateTimeKeys = ["startDate", "endDate", "timeRange"];
|
||||
|
||||
// Default to the last 7 days if no time range is set
|
||||
if (route.name !== "flows/list" && !Object.keys(route.query).some((key) => dateTimeKeys.some((dateTimeKey) => key.includes(dateTimeKey)))) {
|
||||
router.push({query: {...route.query, "filters[timeRange][EQUALS]": "PT168H"}});
|
||||
}
|
||||
|
||||
if (route.name === "flows/update") {
|
||||
filters.value.push({field: "namespace", operation: "EQUALS", value: route.params.namespace});
|
||||
filters.value.push({field: "flowId", operation: "EQUALS", value: route.params.id});
|
||||
|
||||
@@ -2,29 +2,27 @@ import type cytoscape from "cytoscape";
|
||||
|
||||
import {cssVariable} from "@kestra-io/ui-libs";
|
||||
|
||||
const VARIABLES = {
|
||||
import {States} from "./types";
|
||||
|
||||
const VARIABLES: {node: { background: States; border: States }; edge: States;} = {
|
||||
node: {
|
||||
default: {
|
||||
background: "--ks-dependencies-node-background-default",
|
||||
border: "--ks-dependencies-node-border-default",
|
||||
background: {
|
||||
default: "--ks-dependencies-node-background-default",
|
||||
faded: "--ks-dependencies-node-background-faded",
|
||||
selected: "--ks-dependencies-node-background-selected",
|
||||
hovered: "--ks-dependencies-node-background-hovered",
|
||||
},
|
||||
faded: {
|
||||
background: "--ks-dependencies-node-background-faded",
|
||||
border: "--ks-dependencies-node-border-faded",
|
||||
},
|
||||
selected: {
|
||||
background: "--ks-dependencies-node-background-selected",
|
||||
border: "--ks-dependencies-node-border-selected",
|
||||
},
|
||||
hovered: {
|
||||
background: "--ks-dependencies-node-background-hovered",
|
||||
border: "--ks-dependencies-node-border-hovered",
|
||||
border: {
|
||||
default: "--ks-dependencies-node-border-default",
|
||||
faded: "--ks-dependencies-node-border-faded",
|
||||
selected: "--ks-dependencies-node-border-selected",
|
||||
hovered: "--ks-dependencies-node-border-hovered",
|
||||
},
|
||||
},
|
||||
edge: {
|
||||
default: "--ks-dependencies-edge-default",
|
||||
faded: "--ks-dependencies-edge-faded",
|
||||
selected: "--ks-dependencies-node-background-selected",
|
||||
selected: "--ks-dependencies-edge-selected",
|
||||
hovered: "--ks-dependencies-edge-hovered",
|
||||
},
|
||||
};
|
||||
@@ -51,14 +49,14 @@ const edgeAnimated: cytoscape.Css.Edge = {
|
||||
"line-dash-pattern": [3, 5],
|
||||
};
|
||||
|
||||
function nodeColors(type: keyof typeof VARIABLES.node = "default"): Partial<cytoscape.Css.Node> {
|
||||
function nodeColors(type: keyof States = "default"): Partial<cytoscape.Css.Node> {
|
||||
return {
|
||||
"background-color": cssVariable(VARIABLES.node[type].background)!,
|
||||
"border-color": cssVariable(VARIABLES.node[type].border)!,
|
||||
"background-color": cssVariable(VARIABLES.node.background[type])!,
|
||||
"border-color": cssVariable(VARIABLES.node.border[type])!,
|
||||
};
|
||||
}
|
||||
|
||||
export function edgeColors(type: keyof typeof VARIABLES.edge = "default"): Partial<cytoscape.Css.Edge> {
|
||||
export function edgeColors(type: keyof States = "default"): Partial<cytoscape.Css.Edge> {
|
||||
return {
|
||||
"line-color": cssVariable(VARIABLES.edge[type])!,
|
||||
"target-arrow-color": cssVariable(VARIABLES.edge[type])!,
|
||||
|
||||
@@ -35,3 +35,10 @@ export type Edge = {
|
||||
};
|
||||
|
||||
export type Element = { data: Node } | { data: Edge };
|
||||
|
||||
export type States = {
|
||||
default: string;
|
||||
faded: string;
|
||||
selected: string;
|
||||
hovered: string;
|
||||
};
|
||||
|
||||
@@ -482,6 +482,8 @@
|
||||
import {useAuthStore} from "override/stores/auth.ts";
|
||||
import {useFlowStore} from "../../stores/flow.ts";
|
||||
|
||||
import {defaultNamespace} from "../../composables/useNamespaces";
|
||||
|
||||
export default {
|
||||
mixins: [RouteContext, RestoreUrl, DataTableActions, SelectTableActions],
|
||||
components: {
|
||||
@@ -705,15 +707,12 @@
|
||||
}
|
||||
},
|
||||
beforeRouteEnter(to, _, next) {
|
||||
const defaultNamespace = localStorage.getItem(
|
||||
storageKeys.DEFAULT_NAMESPACE,
|
||||
);
|
||||
const query = {...to.query};
|
||||
let queryHasChanged = false;
|
||||
|
||||
const queryKeys = Object.keys(query);
|
||||
if (this?.namespace === undefined && defaultNamespace && !queryKeys.some(key => key.startsWith("filters[namespace]"))) {
|
||||
query["filters[namespace][PREFIX]"] = defaultNamespace;
|
||||
if (this?.namespace === undefined && defaultNamespace() && !queryKeys.some(key => key.startsWith("filters[namespace]"))) {
|
||||
query["filters[namespace][PREFIX]"] = defaultNamespace();
|
||||
queryHasChanged = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -605,7 +605,7 @@
|
||||
.toggle-icon {
|
||||
position: absolute;
|
||||
color: var(--ks-content-alert);
|
||||
right: 1rem;
|
||||
right: 1.5rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
font-size: 1.75rem;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<el-button-group v-else-if="isURI(value)">
|
||||
<el-button
|
||||
type="primary"
|
||||
tag="a"
|
||||
size="small"
|
||||
:href="value"
|
||||
target="_blank"
|
||||
|
||||
@@ -436,6 +436,13 @@
|
||||
|
||||
const monacoEditor = ref<typeof MonacoEditor>();
|
||||
|
||||
let initialRouteName = ref();
|
||||
watch(() => route.name, (newVal) => {
|
||||
if (initialRouteName.value === undefined) {
|
||||
initialRouteName.value = newVal;
|
||||
}
|
||||
}, {immediate: true})
|
||||
|
||||
const updateQuery = () => {
|
||||
const newQuery = {
|
||||
...Object.fromEntries(queryParamsToKeep.value.map(key => {
|
||||
@@ -451,9 +458,11 @@
|
||||
return; // Skip if the query hasn't changed
|
||||
}
|
||||
skipRouteWatcherOnce.value = true;
|
||||
router.push({
|
||||
query: newQuery
|
||||
});
|
||||
if (route.name === initialRouteName.value) {
|
||||
router.push({
|
||||
query: newQuery
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const editorDidMount = (mountedEditor: monaco.editor.IStandaloneCodeEditor) => {
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
import RouteContext from "../../mixins/routeContext";
|
||||
import TopNavBar from "../../components/layout/TopNavBar.vue";
|
||||
import MultiPanelFlowEditorView from "./MultiPanelFlowEditorView.vue";
|
||||
import {storageKeys} from "../../utils/constants";
|
||||
import {useBlueprintsStore} from "../../stores/blueprints";
|
||||
import {useCoreStore} from "../../stores/core";
|
||||
import {editorViewTypes} from "../../utils/constants";
|
||||
@@ -19,6 +18,7 @@
|
||||
import {getRandomID} from "../../../scripts/id";
|
||||
import {useEditorStore} from "../../stores/editor";
|
||||
import {useFlowStore} from "../../stores/flow";
|
||||
import {defaultNamespace} from "../../composables/useNamespaces";
|
||||
|
||||
export default {
|
||||
mixins: [RouteContext],
|
||||
@@ -50,8 +50,7 @@
|
||||
} else if (blueprintId && blueprintSource) {
|
||||
flowYaml = await this.blueprintsStore.getBlueprintSource({type: blueprintSource, kind: "flow", id: blueprintId});
|
||||
} else {
|
||||
const defaultNamespace = localStorage.getItem(storageKeys.DEFAULT_NAMESPACE);
|
||||
const selectedNamespace = this.$route.query.namespace || defaultNamespace || "company.team";
|
||||
const selectedNamespace = this.$route.query.namespace || defaultNamespace() || "company.team";
|
||||
flowYaml = `id: ${getRandomID()}
|
||||
namespace: ${selectedNamespace}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
setTimeout(() => {
|
||||
this.flowStore
|
||||
.loadDependencies({namespace: flow.namespace, id: flow.id}, true)
|
||||
.then(({count}) => this.dependenciesCount = count);
|
||||
.then(({count}) => this.dependenciesCount = count > 0 ? (count - 1) : 0);
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -295,7 +295,6 @@
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import {mapStores} from "pinia";
|
||||
import {useExecutionsStore} from "../../stores/executions";
|
||||
import _merge from "lodash/merge";
|
||||
@@ -314,7 +313,7 @@
|
||||
import MarkdownTooltip from "../layout/MarkdownTooltip.vue";
|
||||
import Kicon from "../Kicon.vue";
|
||||
import Labels from "../layout/Labels.vue";
|
||||
import {storageKeys} from "../../utils/constants";
|
||||
import {defaultNamespace} from "../../composables/useNamespaces";
|
||||
import * as YAML_UTILS from "@kestra-io/ui-libs/flow-yaml-utils";
|
||||
import YAML_CHART from "../dashboard/assets/executions_timeseries_chart.yaml?raw";
|
||||
import {useAuthStore} from "override/stores/auth.ts";
|
||||
@@ -431,7 +430,6 @@
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState("auth", ["user"]),
|
||||
...mapStores(useExecutionsStore, useFlowStore, useAuthStore),
|
||||
user() {
|
||||
return this.authStore.user;
|
||||
@@ -486,14 +484,11 @@
|
||||
}
|
||||
},
|
||||
beforeRouteEnter(to, _, next) {
|
||||
const defaultNamespace = localStorage.getItem(
|
||||
storageKeys.DEFAULT_NAMESPACE,
|
||||
);
|
||||
const query = {...to.query};
|
||||
let queryHasChanged = false;
|
||||
const queryKeys = Object.keys(query);
|
||||
if (defaultNamespace && !queryKeys.some(key => key.startsWith("filters[namespace]"))) {
|
||||
query["filters[namespace][PREFIX]"] = defaultNamespace;
|
||||
if (defaultNamespace() && !queryKeys.some(key => key.startsWith("filters[namespace]"))) {
|
||||
query["filters[namespace][PREFIX]"] = defaultNamespace();
|
||||
queryHasChanged = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -164,12 +164,14 @@
|
||||
|
||||
const noCodeHandlers = useNoCodeHandlers(openTabs, focusTab, tempActions)
|
||||
|
||||
const TABS = isTourRunning.value ? DEFAULT_TOUR_TABS.flatMap(t => t.tabs) : DEFAULT_ACTIVE_TABS;
|
||||
|
||||
const panels = useStorage<Panel[]>(
|
||||
`el-fl-${flowStore.flow?.namespace}-${flowStore.flow?.id}`,
|
||||
DEFAULT_ACTIVE_TABS
|
||||
TABS
|
||||
.map((t) => ({
|
||||
...staticGetPanelFromValue(t).panel,
|
||||
size: 100 / DEFAULT_ACTIVE_TABS.length
|
||||
size: 100 / TABS.length
|
||||
})),
|
||||
undefined,
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
import {useI18n} from "vue-i18n";
|
||||
import CopyToClipboard from "../layout/CopyToClipboard.vue";
|
||||
import Editor from "../inputs/Editor.vue";
|
||||
import {baseUrl, basePathWithoutTenant, apiUrlWithoutTenants} from "../../override/utils/route";
|
||||
import {baseUrl, basePath, apiUrl} from "../../override/utils/route";
|
||||
import {useFlowStore} from "../../stores/flow";
|
||||
|
||||
interface Flow {
|
||||
@@ -73,7 +73,7 @@
|
||||
});
|
||||
|
||||
const generateWebhookUrl = (trigger: Trigger): string => {
|
||||
const origin = baseUrl ? apiUrlWithoutTenants() : `${location.origin}${basePathWithoutTenant()}`;
|
||||
const origin = baseUrl ? apiUrl() : `${location.origin}${basePath()}`;
|
||||
return `${origin}/executions/webhook/${props.flow.namespace}/${props.flow.id}/${trigger.key}`;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="ks-editor edit-flow-editor">
|
||||
<nav v-if="original === undefined && navbar" class="top-nav">
|
||||
<nav v-if="!isDiff && navbar" class="top-nav">
|
||||
<slot name="nav">
|
||||
<div class="text-nowrap">
|
||||
<el-button-group>
|
||||
@@ -43,11 +43,12 @@
|
||||
<div ref="editorContainer" class="editor-wrapper position-relative">
|
||||
<MonacoEditor
|
||||
ref="monacoEditor"
|
||||
:key="isDiff.toString()"
|
||||
:path="path"
|
||||
:theme="themeComputed"
|
||||
:value="modelValue"
|
||||
:options="options"
|
||||
:diff-editor="original !== undefined"
|
||||
:diff-editor="isDiff"
|
||||
:original="original"
|
||||
:language="lang"
|
||||
:extension="extension"
|
||||
@@ -230,8 +231,8 @@
|
||||
|
||||
} else {
|
||||
options.scrollbar = {
|
||||
vertical: props.original !== undefined ? "hidden" : "auto",
|
||||
verticalScrollbarSize: props.original !== undefined ? 0 : 10,
|
||||
vertical: isDiff.value ? "hidden" : "auto",
|
||||
verticalScrollbarSize: isDiff.value ? 0 : 10,
|
||||
alwaysConsumeMouseWheel: false,
|
||||
};
|
||||
options.renderSideBySide = props.diffSideBySide;
|
||||
@@ -282,6 +283,7 @@
|
||||
let lastTimeout: number | undefined = undefined
|
||||
let decorations: monaco.editor.IEditorDecorationsCollection | undefined = undefined
|
||||
|
||||
const isDiff = computed(() => props.original !== undefined);
|
||||
|
||||
function isCodeEditor(editor?: monaco.editor.IStandaloneCodeEditor | monaco.editor.IStandaloneDiffEditor): editor is monaco.editor.IStandaloneCodeEditor{
|
||||
return editor?.getEditorType() === monacoEditor.value?.monaco.editor.EditorType.ICodeEditor
|
||||
@@ -310,7 +312,7 @@
|
||||
return
|
||||
}
|
||||
|
||||
if (!props.original) {
|
||||
if (!isDiff.value) {
|
||||
editor.onDidBlurEditorWidget?.(() => {
|
||||
emit("focusout", isCodeEditor(editor)
|
||||
? editor.getValue()
|
||||
@@ -396,7 +398,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (props.original === undefined && props.navbar && props.fullHeight) {
|
||||
if (!isDiff.value && props.navbar && props.fullHeight) {
|
||||
editor.addAction({
|
||||
id: "fold-multiline",
|
||||
label: t("fold_all_multi_lines"),
|
||||
@@ -445,7 +447,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
if (!props.original) {
|
||||
if (!isDiff.value) {
|
||||
editor.onDidContentSizeChange((_) => {
|
||||
highlightPebble();
|
||||
});
|
||||
@@ -652,197 +654,197 @@
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../code/styles/code.scss";
|
||||
@import "../code/styles/code.scss";
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@kestra-io/ui-libs/src/scss/color-palette.scss";
|
||||
@import "../../styles/layout/root-dark.scss";
|
||||
@import "@kestra-io/ui-libs/src/scss/color-palette.scss";
|
||||
@import "../../styles/layout/root-dark.scss";
|
||||
|
||||
.highlight-lines{
|
||||
background-color: rgba($base-blue-400, .2);
|
||||
}
|
||||
|
||||
.editor-content-widget-content{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.el-button-group {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
|
||||
:not(.namespace-defaults, .el-drawer__body) > .ks-editor {
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.el-form .ks-editor {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ks-editor {
|
||||
display: flex;
|
||||
|
||||
.top-nav {
|
||||
background-color: var(--ks-background-card);
|
||||
padding: 0.5rem;
|
||||
border-radius: var(--bs-border-radius-lg);
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
||||
html.dark & {
|
||||
background-color: var(--bs-gray-100);
|
||||
}
|
||||
.highlight-lines{
|
||||
background-color: rgba($base-blue-400, .2);
|
||||
}
|
||||
|
||||
.editor-absolute-container {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 20px;
|
||||
z-index: 10;
|
||||
color: var(--ks-content-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.editor-absolute-container > * {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
.editor-content-widget-content{
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.single-line {
|
||||
min-height: var(--el-component-size);
|
||||
padding: 1px 11px;
|
||||
background-color: var(
|
||||
--el-input-bg-color,
|
||||
var(--el-fill-color-blank)
|
||||
);
|
||||
border-radius: var(
|
||||
--el-input-border-radius,
|
||||
var(--el-border-radius-base)
|
||||
);
|
||||
transition: var(--el-transition-box-shadow);
|
||||
box-shadow: 0 0 0 1px var(--ks-border-primary) inset;
|
||||
padding-top: 7px;
|
||||
.el-button-group {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
|
||||
&.custom-dark-vs-theme {
|
||||
background-color: var(--ks-background-input);
|
||||
}
|
||||
:not(.namespace-defaults, .el-drawer__body) > .ks-editor {
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.theme-light {
|
||||
background-color: $base-white;
|
||||
.el-form .ks-editor {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ks-editor {
|
||||
display: flex;
|
||||
|
||||
.top-nav {
|
||||
background-color: var(--ks-background-card);
|
||||
padding: 0.5rem;
|
||||
border-radius: var(--bs-border-radius-lg);
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
||||
html.dark & {
|
||||
background-color: var(--bs-gray-100);
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
.editor-absolute-container {
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
overflow: hidden;
|
||||
padding-left: inherit;
|
||||
padding-right: inherit;
|
||||
cursor: text;
|
||||
user-select: none;
|
||||
color: var(--ks-content-inactive);
|
||||
top: 8px;
|
||||
right: 20px;
|
||||
z-index: 10;
|
||||
color: var(--ks-content-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.editor-wrapper {
|
||||
min-width: 75%;
|
||||
width: 100%;
|
||||
.editor-absolute-container > * {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.monaco-hover-content {
|
||||
h4 {
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: bold;
|
||||
line-height: var(--bs-body-line-height);
|
||||
.editor-container {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
|
||||
&.single-line {
|
||||
min-height: var(--el-component-size);
|
||||
padding: 1px 11px;
|
||||
background-color: var(
|
||||
--el-input-bg-color,
|
||||
var(--el-fill-color-blank)
|
||||
);
|
||||
border-radius: var(
|
||||
--el-input-border-radius,
|
||||
var(--el-border-radius-base)
|
||||
);
|
||||
transition: var(--el-transition-box-shadow);
|
||||
box-shadow: 0 0 0 1px var(--ks-border-primary) inset;
|
||||
padding-top: 7px;
|
||||
|
||||
&.custom-dark-vs-theme {
|
||||
background-color: var(--ks-background-input);
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0.5rem;
|
||||
&.theme-light {
|
||||
background-color: $base-white;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
display: none;
|
||||
.placeholder {
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
overflow: hidden;
|
||||
padding-left: inherit;
|
||||
padding-right: inherit;
|
||||
cursor: text;
|
||||
user-select: none;
|
||||
color: var(--ks-content-inactive);
|
||||
}
|
||||
|
||||
.editor-wrapper {
|
||||
min-width: 75%;
|
||||
width: 100%;
|
||||
|
||||
.monaco-hover-content {
|
||||
h4 {
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: bold;
|
||||
line-height: var(--bs-body-line-height);
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
&:last-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
*:nth-last-child(2n) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*:nth-last-child(2n) {
|
||||
margin-bottom: 0;
|
||||
.bottom-right {
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
//gap: .5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-right {
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
.custom-dark-vs-theme {
|
||||
.monaco-editor,
|
||||
.monaco-editor-background {
|
||||
outline: none;
|
||||
background-color: var(--ks-background-input);
|
||||
--vscode-editor-background: var(--ks-background-input);
|
||||
--vscode-breadcrumb-background: var(--ks-background-input);
|
||||
--vscode-editorGutter-background: var(--ks-background-input);
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
//gap: .5rem;
|
||||
.monaco-editor .margin {
|
||||
background-color: var(--ks-background-input);
|
||||
--vscode-editorGutter-background: var(--ks-background-input);
|
||||
--vscode-editorLineNumber-activeForeground: var(--ks-content-secondary);
|
||||
--vscode-editorLineNumber-foreground: var(--ks-content-secondary);
|
||||
--vscode-editorLineNumber-rangeHighlightBackground: var(--ks-content-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.highlight-text {
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 19px 44px rgba(157, 29, 236, 0.31);
|
||||
|
||||
html.dark & {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.highlight-pebble {
|
||||
color: #977100 !important;
|
||||
|
||||
html.dark & {
|
||||
color: #ffca16 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.disable-text {
|
||||
color: var(--ks-content-inactive) !important;
|
||||
}
|
||||
|
||||
div.img {
|
||||
min-height: 130px;
|
||||
height: 100%;
|
||||
|
||||
&.get-started {
|
||||
background: url("../../assets/onboarding/onboarding-doc-light.svg")
|
||||
no-repeat center;
|
||||
|
||||
html.dark & {
|
||||
background: url("../../assets/onboarding/onboarding-doc-dark.svg")
|
||||
no-repeat center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-dark-vs-theme {
|
||||
.monaco-editor,
|
||||
.monaco-editor-background {
|
||||
outline: none;
|
||||
background-color: var(--ks-background-input);
|
||||
--vscode-editor-background: var(--ks-background-input);
|
||||
--vscode-breadcrumb-background: var(--ks-background-input);
|
||||
--vscode-editorGutter-background: var(--ks-background-input);
|
||||
}
|
||||
|
||||
.monaco-editor .margin {
|
||||
background-color: var(--ks-background-input);
|
||||
--vscode-editorGutter-background: var(--ks-background-input);
|
||||
--vscode-editorLineNumber-activeForeground: var(--ks-content-secondary);
|
||||
--vscode-editorLineNumber-foreground: var(--ks-content-secondary);
|
||||
--vscode-editorLineNumber-rangeHighlightBackground: var(--ks-content-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.highlight-text {
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 19px 44px rgba(157, 29, 236, 0.31);
|
||||
|
||||
html.dark & {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.highlight-pebble {
|
||||
color: #977100 !important;
|
||||
|
||||
html.dark & {
|
||||
color: #ffca16 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.disable-text {
|
||||
color: var(--ks-content-inactive) !important;
|
||||
}
|
||||
|
||||
div.img {
|
||||
min-height: 130px;
|
||||
height: 100%;
|
||||
|
||||
&.get-started {
|
||||
background: url("../../assets/onboarding/onboarding-doc-light.svg")
|
||||
no-repeat center;
|
||||
|
||||
html.dark & {
|
||||
background: url("../../assets/onboarding/onboarding-doc-dark.svg")
|
||||
no-repeat center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
id="editorWrapper"
|
||||
ref="editorRefElement"
|
||||
class="flex-1"
|
||||
:model-value="draftSource === undefined ? source : draftSource"
|
||||
:model-value="hasDraft ? draftSource : source"
|
||||
:schema-type="isCurrentTabFlow ? 'flow': undefined"
|
||||
:lang="extension === undefined ? 'yaml' : undefined"
|
||||
:extension="extension"
|
||||
@@ -19,7 +19,7 @@
|
||||
@execute="execute"
|
||||
@mouse-move="(e) => highlightHoveredTask(e.target?.position?.lineNumber)"
|
||||
@mouse-leave="() => highlightHoveredTask(-1)"
|
||||
:original="draftSource === undefined ? undefined : source"
|
||||
:original="hasDraft ? source : undefined"
|
||||
:diff-side-by-side="false"
|
||||
>
|
||||
<template #absolute>
|
||||
@@ -45,7 +45,7 @@
|
||||
/>
|
||||
</Transition>
|
||||
<AcceptDecline
|
||||
v-if="draftSource !== undefined"
|
||||
v-if="hasDraft"
|
||||
@accept="acceptDraft"
|
||||
@reject="declineDraft"
|
||||
/>
|
||||
@@ -120,7 +120,7 @@
|
||||
const content = await namespacesStore.readFile({namespace: fileNamespace.toString(), path: props.path ?? ""})
|
||||
editorStore.setTabContent({path: props.path, content})
|
||||
}
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
loadPluginsHash();
|
||||
@@ -162,13 +162,13 @@
|
||||
hash.value = config.pluginsHash;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function editorUpdate(newValue: string){
|
||||
if (editorContent.value === newValue) {
|
||||
return;
|
||||
}
|
||||
if (isCurrentTabFlow.value) {
|
||||
if (draftSource.value !== undefined) {
|
||||
if (hasDraft.value) {
|
||||
draftSource.value = newValue;
|
||||
} else {
|
||||
flowStore.flowYaml = newValue;
|
||||
@@ -292,6 +292,8 @@
|
||||
aiAgentOpened.value = true;
|
||||
}
|
||||
|
||||
const hasDraft = computed(() => draftSource.value !== undefined);
|
||||
|
||||
const {
|
||||
playgroundStore,
|
||||
highlightHoveredTask,
|
||||
|
||||
@@ -727,7 +727,10 @@
|
||||
});
|
||||
|
||||
if (editorRef.value) {
|
||||
localEditor.value = monaco.editor.create(editorRef.value, options);
|
||||
localEditor.value = monaco.editor.create(editorRef.value, {
|
||||
...options,
|
||||
fixedOverflowWidgets: true // Helps suggestion widget render above other elements
|
||||
});
|
||||
|
||||
if (props.suggestionsOnFocus) {
|
||||
localEditor.value.onMouseDown(() => {
|
||||
|
||||
@@ -41,12 +41,12 @@
|
||||
import ArrowRight from "vue-material-design-icons/ArrowRight.vue";
|
||||
|
||||
const router = useRouter();
|
||||
const {generateMenu} = useLeftMenu()
|
||||
const {menu} = useLeftMenu()
|
||||
|
||||
const filter = ref("");
|
||||
|
||||
const navItems = computed(() => {
|
||||
return generateMenu().flatMap(item => {
|
||||
return menu.value.flatMap(item => {
|
||||
if(item.hidden) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
ref="sideBarRef"
|
||||
data-component="FILENAME_PLACEHOLDER"
|
||||
id="side-menu"
|
||||
:menu="localMenu"
|
||||
:menu
|
||||
@update:collapsed="onToggleCollapse"
|
||||
width="268px"
|
||||
:collapsed="collapsed"
|
||||
@@ -29,11 +29,9 @@
|
||||
</sidebar-menu>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
watch,
|
||||
onUpdated,
|
||||
onMounted,
|
||||
ref,
|
||||
computed,
|
||||
shallowRef, h
|
||||
@@ -50,34 +48,20 @@
|
||||
import Environment from "./Environment.vue";
|
||||
import BookmarkLinkList from "./BookmarkLinkList.vue";
|
||||
import {useBookmarksStore} from "../../stores/bookmarks";
|
||||
import type {MenuItem} from "override/components/useLeftMenu.js";
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
generateMenu: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
showLink: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
const props = withDefaults(defineProps<{
|
||||
menu: MenuItem[],
|
||||
showLink: boolean
|
||||
}>(), {
|
||||
showLink: true
|
||||
})
|
||||
|
||||
const $emit = defineEmits(["menu-collapse"])
|
||||
|
||||
const $route = useRoute()
|
||||
const {locale, t} = useI18n({useScope: "global"});
|
||||
|
||||
function flattenMenu(menu) {
|
||||
return menu.reduce((acc, item) => {
|
||||
if (item.child) {
|
||||
acc.push(...flattenMenu(item.child));
|
||||
}
|
||||
|
||||
acc.push(item);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
const {t} = useI18n({useScope: "global"});
|
||||
|
||||
function onToggleCollapse(folded) {
|
||||
collapsed.value = folded;
|
||||
@@ -136,43 +120,11 @@
|
||||
component: () => h(BookmarkLinkList, {pages: bookmarksStore.pages}),
|
||||
}]
|
||||
}] : []),
|
||||
...disabledCurrentRoute(props.generateMenu())
|
||||
...disabledCurrentRoute(props.menu)
|
||||
];
|
||||
});
|
||||
|
||||
|
||||
watch(locale, () => {
|
||||
localMenu.value = menu.value;
|
||||
}, {deep: true});
|
||||
|
||||
/**
|
||||
* @type {import("vue").Ref<typeof import('vue-sidebar-menu').SidebarMenu>}
|
||||
*/
|
||||
const sideBarRef = ref(null);
|
||||
|
||||
watch(menu, (newVal, oldVal) => {
|
||||
// Check if the active menu item has changed, if yes then update the menu
|
||||
if (JSON.stringify(flattenMenu(newVal).map(e => e.class?.includes("vsm--link_active") ?? false)) !==
|
||||
JSON.stringify(flattenMenu(oldVal).map(e => e.class?.includes("vsm--link_active") ?? false))) {
|
||||
localMenu.value = newVal;
|
||||
sideBarRef.value?.$el.querySelectorAll(".vsm--item span").forEach(e => {
|
||||
//empty icon name on mouseover
|
||||
e.setAttribute("title", "")
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
flush: "post",
|
||||
deep: true
|
||||
});
|
||||
|
||||
const collapsed = ref(localStorage.getItem("menuCollapsed") === "true")
|
||||
const localMenu = ref([])
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
localMenu.value = menu.value;
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -193,6 +193,7 @@
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
div.line {
|
||||
position: relative;
|
||||
cursor: text;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
@@ -274,5 +275,16 @@ div.line {
|
||||
border: 1px solid var(--ks-border-primary);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
:deep(.clipboard) {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
&:hover :deep(.clipboard) {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
import * as YAML_UTILS from "@kestra-io/ui-libs/flow-yaml-utils";
|
||||
import YAML_CHART from "../dashboard/assets/logs_timeseries_chart.yaml?raw";
|
||||
import {useLogsStore} from "../../stores/logs";
|
||||
import {defaultNamespace} from "../../composables/useNamespaces";
|
||||
|
||||
export default {
|
||||
mixins: [RouteContext, RestoreUrl, DataTableActions],
|
||||
@@ -147,15 +148,12 @@
|
||||
}
|
||||
},
|
||||
beforeRouteEnter(to, _, next) {
|
||||
const defaultNamespace = localStorage.getItem(
|
||||
storageKeys.DEFAULT_NAMESPACE,
|
||||
);
|
||||
const query = {...to.query};
|
||||
let queryHasChanged = false;
|
||||
|
||||
const queryKeys = Object.keys(query);
|
||||
if (defaultNamespace && !queryKeys.some(key => key.startsWith("filters[namespace]"))) {
|
||||
query["filters[namespace][PREFIX]"] = defaultNamespace;
|
||||
if (defaultNamespace() && !queryKeys.some(key => key.startsWith("filters[namespace]"))) {
|
||||
query["filters[namespace][PREFIX]"] = defaultNamespace();
|
||||
queryHasChanged = true;
|
||||
}
|
||||
|
||||
@@ -170,12 +168,6 @@
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
LogFilterLanguage() {
|
||||
return LogFilterLanguage
|
||||
},
|
||||
onDateFilterTypeChange(event) {
|
||||
this.canAutoRefresh = event;
|
||||
},
|
||||
showStatChart() {
|
||||
return this.showChart;
|
||||
},
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
const main = document.querySelector("main");
|
||||
if(main) main.scrollTop = 0;
|
||||
|
||||
if (namespace.value) {
|
||||
namespacesStore.load(namespace.value);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
import {useNamespacesStore} from "override/stores/namespaces"
|
||||
import DotsSquare from "vue-material-design-icons/DotsSquare.vue"
|
||||
import Lock from "vue-material-design-icons/Lock.vue";
|
||||
import {storageKeys} from "../../../utils/constants";
|
||||
import {defaultNamespace} from "../../../composables/useNamespaces";
|
||||
|
||||
const {t} = useI18n();
|
||||
|
||||
@@ -79,13 +79,13 @@
|
||||
|
||||
onMounted(() => {
|
||||
if (modelValue.value === undefined || modelValue.value.length === 0) {
|
||||
const defaultNamespace = localStorage.getItem(storageKeys.DEFAULT_NAMESPACE);
|
||||
const defaultNamespaceVal = defaultNamespace();
|
||||
if (Array.isArray(modelValue.value)) {
|
||||
if (defaultNamespace != null) {
|
||||
modelValue.value = [defaultNamespace];
|
||||
if (defaultNamespaceVal != null) {
|
||||
modelValue.value = [defaultNamespaceVal];
|
||||
}
|
||||
} else {
|
||||
modelValue.value = defaultNamespace ?? modelValue.value;
|
||||
modelValue.value = defaultNamespaceVal ?? modelValue.value;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -277,6 +277,7 @@
|
||||
import Column from "./components/block/Column.vue"
|
||||
import {useAuthStore} from "override/stores/auth"
|
||||
import {useFlowStore} from "../../stores/flow"
|
||||
import {defaultNamespace} from "../../composables/useNamespaces";
|
||||
|
||||
export const DATE_FORMAT_STORAGE_KEY = "dateFormat";
|
||||
export const TIMEZONE_STORAGE_KEY = "timezone";
|
||||
@@ -342,7 +343,7 @@
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.pendingSettings.defaultNamespace = localStorage.getItem("defaultNamespace") || "company.team";
|
||||
this.pendingSettings.defaultNamespace = defaultNamespace();
|
||||
this.pendingSettings.editorType = localStorage.getItem(storageKeys.EDITOR_VIEW_TYPE) || "YAML";
|
||||
this.pendingSettings.defaultLogLevel = localStorage.getItem("defaultLogLevel") || "INFO";
|
||||
this.pendingSettings.lang = Utils.getLang();
|
||||
|
||||
@@ -173,9 +173,6 @@
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onDateFilterTypeChange(event) {
|
||||
this.canAutoRefresh = event;
|
||||
},
|
||||
isRunning(item){
|
||||
return State.isRunning(item.state.current);
|
||||
},
|
||||
|
||||
@@ -47,14 +47,14 @@ export function useDataTableActions(options: DataTableActionsOptions = {}) {
|
||||
const embed = computed(() => options.embed);
|
||||
const dataTableRef = computed(() => options.dataTableRef?.value);
|
||||
|
||||
const sortString = (sortItem: SortItem): string | undefined => {
|
||||
const sortString = (sortItem: SortItem, sortKeyMapper: (k: string) => string): string | undefined => {
|
||||
if (sortItem && sortItem.prop && sortItem.order) {
|
||||
return `${sortItem.prop}:${sortItem.order === "descending" ? "desc" : "asc"}`;
|
||||
return `${sortKeyMapper(sortItem.prop)}:${sortItem.order === "descending" ? "desc" : "asc"}`;
|
||||
}
|
||||
};
|
||||
|
||||
const onSort = (sortItem: SortItem) => {
|
||||
internalSort.value = sortString(sortItem);
|
||||
const onSort = (sortItem: SortItem, sortKeyMapper = (k: string) => k) => {
|
||||
internalSort.value = sortString(sortItem, sortKeyMapper);
|
||||
|
||||
if (internalSort.value) {
|
||||
const sort = internalSort.value;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {Store} from "vuex";
|
||||
import {EntityIterator} from "./entityIterator.ts";
|
||||
import {useNamespacesStore} from "override/stores/namespaces.ts";
|
||||
import {storageKeys} from "../utils/constants.ts";
|
||||
|
||||
export interface Namespace {
|
||||
id: string;
|
||||
@@ -22,6 +23,10 @@ export class NamespaceIterator extends EntityIterator<Namespace>{
|
||||
}
|
||||
}
|
||||
|
||||
export function defaultNamespace() {
|
||||
return localStorage.getItem(storageKeys.DEFAULT_NAMESPACE);
|
||||
}
|
||||
|
||||
export default function useNamespaces(store: Store<any>, fetchSize: number, options?: any): NamespaceIterator {
|
||||
return new NamespaceIterator(store, fetchSize, options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {computed, nextTick, onMounted, ref} from "vue";
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import {defaultNamespace} from "./useNamespaces.ts";
|
||||
|
||||
interface UseRestoreUrlOptions {
|
||||
restoreUrl?: boolean;
|
||||
@@ -57,8 +58,8 @@ export default function useRestoreUrl(options: UseRestoreUrlOptions = {}) {
|
||||
|
||||
let change = false;
|
||||
|
||||
if (!localExist && isDefaultNamespaceAllow && localStorage.getItem("defaultNamespace")) {
|
||||
local["namespace"] = localStorage.getItem("defaultNamespace");
|
||||
if (!localExist && isDefaultNamespaceAllow && defaultNamespace()) {
|
||||
local["namespace"] = defaultNamespace();
|
||||
}
|
||||
|
||||
for (const key in local) {
|
||||
|
||||
@@ -44,13 +44,13 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
sortString(sortItem) {
|
||||
sortString(sortItem, sortKeyMapper) {
|
||||
if (sortItem && sortItem.prop && sortItem.order) {
|
||||
return `${sortItem.prop}:${sortItem.order === "descending" ? "desc" : "asc"}`;
|
||||
return `${sortKeyMapper(sortItem.prop)}:${sortItem.order === "descending" ? "desc" : "asc"}`;
|
||||
}
|
||||
},
|
||||
onSort(sortItem) {
|
||||
this.internalSort = this.sortString(sortItem);
|
||||
onSort(sortItem, sortKeyMapper = (k) => k) {
|
||||
this.internalSort = this.sortString(sortItem, sortKeyMapper);
|
||||
|
||||
if (this.internalSort) {
|
||||
const sort = this.internalSort;
|
||||
|
||||
@@ -165,10 +165,9 @@ export default {
|
||||
.then(message => {
|
||||
this.$toast()
|
||||
.confirm(message, () => {
|
||||
// TODO: When flow store is migrated to Pinia, this will be simplified:
|
||||
const deletePromise = this.dataType === "template"
|
||||
? this.templateStore.deleteTemplate(item)
|
||||
: this.$store.dispatch(`${this.dataType}/delete${this.dataType.capitalize()}`, item);
|
||||
: this.flowStore.deleteFlow(item);
|
||||
|
||||
return deletePromise
|
||||
.then(() => {
|
||||
@@ -249,7 +248,7 @@ export default {
|
||||
// TODO: When flow store is migrated to Pinia, this will be simplified:
|
||||
const createPromise = this.dataType === "template"
|
||||
? this.templateStore.createTemplate({template: this.content})
|
||||
: this.$store.dispatch(`${this.dataType}/create${this.dataType.capitalize()}`, {[this.dataType]: this.content});
|
||||
: this.flowStore.createFlow({flow: this.content});
|
||||
|
||||
createPromise
|
||||
.then((data) => {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import {defaultNamespace} from "../composables/useNamespaces.js";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
restoreUrl: {
|
||||
@@ -55,8 +57,8 @@ export default {
|
||||
|
||||
let change = false
|
||||
|
||||
if (!localExist && this.isDefaultNamespaceAllow && localStorage.getItem("defaultNamespace")) {
|
||||
local["namespace"] = localStorage.getItem("defaultNamespace");
|
||||
if (!localExist && this.isDefaultNamespaceAllow && defaultNamespace()) {
|
||||
local["namespace"] = defaultNamespace();
|
||||
}
|
||||
|
||||
for (const key in local) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<side-bar :generate-menu="generateMenu" :show-link="showLink" @menu-collapse="onCollapse">
|
||||
<side-bar v-if="menu" :menu :show-link="showLink" @menu-collapse="onCollapse">
|
||||
<template #footer>
|
||||
<auth />
|
||||
<auth />
|
||||
</template>
|
||||
</side-bar>
|
||||
</template>
|
||||
@@ -19,22 +19,22 @@
|
||||
$emit("menu-collapse", folded);
|
||||
}
|
||||
|
||||
const {generateMenu} = useLeftMenu();
|
||||
const {menu} = useLeftMenu();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#side-menu {
|
||||
.el-select {
|
||||
padding: 0 30px;
|
||||
padding-bottom: 15px;
|
||||
transition: all 0.2s ease;
|
||||
background-color: transparent;
|
||||
}
|
||||
&.vsm_collapsed {
|
||||
#side-menu {
|
||||
.el-select {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
padding: 0 30px;
|
||||
padding-bottom: 15px;
|
||||
transition: all 0.2s ease;
|
||||
background-color: transparent;
|
||||
}
|
||||
&.vsm_collapsed {
|
||||
.el-select {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
import {useRoute} from "vue-router";
|
||||
import useRouteContext from "../../../mixins/useRouteContext.ts";
|
||||
import {useStore} from "vuex";
|
||||
import useNamespaces, {Namespace} from "../../../composables/useNamespaces.ts";
|
||||
import useNamespaces, {Namespace} from "../../../composables/useNamespaces";
|
||||
import {useI18n} from "vue-i18n";
|
||||
import {useMiscStore} from "override/stores/misc";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {shallowRef} from "vue";
|
||||
import {computed} from "vue";
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import {useI18n} from "vue-i18n";
|
||||
import {useMiscStore} from "override/stores/misc";
|
||||
@@ -20,6 +20,8 @@ import DatabaseOutline from "vue-material-design-icons/DatabaseOutline.vue";
|
||||
import ShieldKeyOutline from "vue-material-design-icons/ShieldKeyOutline.vue";
|
||||
import FlaskOutline from "vue-material-design-icons/FlaskOutline.vue";
|
||||
|
||||
export type MenuItem = {href?: {name: string, params?: Record<string, any>, query?: Record<string, any>}, child?: MenuItem[]};
|
||||
|
||||
export function useLeftMenu() {
|
||||
const {t} = useI18n({useScope: "global"});
|
||||
const $route = useRoute();
|
||||
@@ -40,11 +42,12 @@ export function useLeftMenu() {
|
||||
.map((r) => r.name);
|
||||
}
|
||||
|
||||
// This object seems to be a good candidate for a computed value
|
||||
// but cannot be. When it becomes a computed, the hack to set current
|
||||
// route as active in the blueprints activates pages forever.
|
||||
const generateMenu = () => {
|
||||
return [
|
||||
const flatMenuItems = (items: MenuItem[]): MenuItem[] => {
|
||||
return items.flatMap(item => item.child ? [item, ...flatMenuItems(item.child)] : [item])
|
||||
}
|
||||
|
||||
const menu = computed(() => {
|
||||
const generatedMenu = [
|
||||
{
|
||||
href: {
|
||||
name: "home",
|
||||
@@ -52,7 +55,7 @@ export function useLeftMenu() {
|
||||
},
|
||||
title: t("dashboards.labels.plural"),
|
||||
icon: {
|
||||
element: shallowRef(ViewDashboardVariantOutline),
|
||||
element: ViewDashboardVariantOutline,
|
||||
class: "menu-icon",
|
||||
},
|
||||
},
|
||||
@@ -61,7 +64,7 @@ export function useLeftMenu() {
|
||||
routes: routeStartWith("flows"),
|
||||
title: t("flows"),
|
||||
icon: {
|
||||
element: shallowRef(FileTreeOutline),
|
||||
element: FileTreeOutline,
|
||||
class: "menu-icon",
|
||||
},
|
||||
exact: false,
|
||||
@@ -71,7 +74,7 @@ export function useLeftMenu() {
|
||||
routes: routeStartWith("apps"),
|
||||
title: t("apps"),
|
||||
icon: {
|
||||
element: shallowRef(FormatListGroupPlus),
|
||||
element: FormatListGroupPlus,
|
||||
class: "menu-icon",
|
||||
},
|
||||
attributes: {
|
||||
@@ -83,7 +86,7 @@ export function useLeftMenu() {
|
||||
routes: routeStartWith("templates"),
|
||||
title: t("templates"),
|
||||
icon: {
|
||||
element: shallowRef(ContentCopy),
|
||||
element: ContentCopy,
|
||||
class: "menu-icon",
|
||||
},
|
||||
hidden: !miscStore.configs?.isTemplateEnabled,
|
||||
@@ -93,7 +96,7 @@ export function useLeftMenu() {
|
||||
routes: routeStartWith("executions"),
|
||||
title: t("executions"),
|
||||
icon: {
|
||||
element: shallowRef(TimelineClockOutline),
|
||||
element: TimelineClockOutline,
|
||||
class: "menu-icon",
|
||||
},
|
||||
},
|
||||
@@ -102,7 +105,7 @@ export function useLeftMenu() {
|
||||
routes: routeStartWith("taskruns"),
|
||||
title: t("taskruns"),
|
||||
icon: {
|
||||
element: shallowRef(ChartTimeline),
|
||||
element: ChartTimeline,
|
||||
class: "menu-icon",
|
||||
},
|
||||
hidden: !miscStore.configs?.isTaskRunEnabled,
|
||||
@@ -112,7 +115,7 @@ export function useLeftMenu() {
|
||||
routes: routeStartWith("logs"),
|
||||
title: t("logs"),
|
||||
icon: {
|
||||
element: shallowRef(TimelineTextOutline),
|
||||
element: TimelineTextOutline,
|
||||
class: "menu-icon",
|
||||
},
|
||||
},
|
||||
@@ -121,7 +124,7 @@ export function useLeftMenu() {
|
||||
routes: routeStartWith("tests"),
|
||||
title: t("demos.tests.label"),
|
||||
icon: {
|
||||
element: shallowRef(FlaskOutline),
|
||||
element: FlaskOutline,
|
||||
class: "menu-icon"
|
||||
},
|
||||
attributes: {
|
||||
@@ -133,7 +136,7 @@ export function useLeftMenu() {
|
||||
routes: routeStartWith("namespaces"),
|
||||
title: t("namespaces"),
|
||||
icon: {
|
||||
element: shallowRef(DotsSquare),
|
||||
element: DotsSquare,
|
||||
class: "menu-icon",
|
||||
},
|
||||
},
|
||||
@@ -142,7 +145,7 @@ export function useLeftMenu() {
|
||||
routes: routeStartWith("kv"),
|
||||
title: t("kv.name"),
|
||||
icon: {
|
||||
element: shallowRef(DatabaseOutline),
|
||||
element: DatabaseOutline,
|
||||
class: "menu-icon",
|
||||
},
|
||||
},
|
||||
@@ -151,7 +154,7 @@ export function useLeftMenu() {
|
||||
routes: routeStartWith("secrets"),
|
||||
title: t("secret.names"),
|
||||
icon: {
|
||||
element: shallowRef(ShieldKeyOutline),
|
||||
element: ShieldKeyOutline,
|
||||
class: "menu-icon",
|
||||
},
|
||||
attributes: {
|
||||
@@ -162,7 +165,7 @@ export function useLeftMenu() {
|
||||
routes: routeStartWith("blueprints"),
|
||||
title: t("blueprints.title"),
|
||||
icon: {
|
||||
element: shallowRef(BallotOutline),
|
||||
element: BallotOutline,
|
||||
class: "menu-icon",
|
||||
},
|
||||
child: [
|
||||
@@ -200,7 +203,7 @@ export function useLeftMenu() {
|
||||
routes: routeStartWith("plugins"),
|
||||
title: t("plugins.names"),
|
||||
icon: {
|
||||
element: shallowRef(Connection),
|
||||
element: Connection,
|
||||
class: "menu-icon",
|
||||
},
|
||||
},
|
||||
@@ -208,7 +211,7 @@ export function useLeftMenu() {
|
||||
title: t("administration"),
|
||||
routes: routeStartWith("admin"),
|
||||
icon: {
|
||||
element: shallowRef(ShieldAccountVariantOutline),
|
||||
element: ShieldAccountVariantOutline,
|
||||
class: "menu-icon",
|
||||
},
|
||||
child: [
|
||||
@@ -257,10 +260,18 @@ export function useLeftMenu() {
|
||||
],
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
flatMenuItems(generatedMenu).forEach(menuItem => {
|
||||
if (menuItem.href !== undefined && menuItem.href?.name === $route.name) {
|
||||
menuItem.href.query = {...$route.query, ...menuItem.href?.query};
|
||||
}
|
||||
});
|
||||
|
||||
return generatedMenu;
|
||||
});
|
||||
|
||||
return {
|
||||
routeStartWith,
|
||||
generateMenu
|
||||
menu
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export const baseUrl = createBaseUrl().replace(/\/$/, "")
|
||||
export const basePath = () => "/api/v1/main"
|
||||
export const basePathWithoutTenant = () => "/api/v1"
|
||||
|
||||
export const apiUrl = (_: Store<any>): string => {
|
||||
export const apiUrl = (_?: Store<any>): string => {
|
||||
return `${baseUrl}${basePath()}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,19 @@ import DemoInstance from "../components/demo/Instance.vue"
|
||||
import DemoApps from "../components/demo/Apps.vue"
|
||||
import DemoTests from "../components/demo/Tests.vue"
|
||||
|
||||
function maybeAddTimeRangeFilter(to) {
|
||||
const dateTimeKeys = ["startDate", "endDate", "timeRange"];
|
||||
|
||||
// Default to the last 7 days if no time range is set
|
||||
if (!Object.keys(to.query).some((key) => dateTimeKeys.some((dateTimeKey) => key.includes(dateTimeKey)))) {
|
||||
to.query["filters[timeRange][EQUALS]"] = "PT168H";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export default [
|
||||
//Initial
|
||||
{name: "root", path: "/", redirect: {name: "home"}, meta: {layout: {template: "<div />"}}},
|
||||
@@ -19,6 +32,15 @@ export default [
|
||||
path: "/:tenant?/dashboards/:dashboard?",
|
||||
component: () => import("../components/dashboard/Dashboard.vue"),
|
||||
beforeEnter: (to, from, next) => {
|
||||
if (maybeAddTimeRangeFilter(to)) {
|
||||
next({
|
||||
name: to.name,
|
||||
params: to.params,
|
||||
query: to.query,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!to.params.dashboard) {
|
||||
next({
|
||||
name: "home",
|
||||
@@ -43,7 +65,23 @@ export default [
|
||||
{name: "flows/update", path: "/:tenant?/flows/edit/:namespace/:id/:tab?", component: () => import("../components/flows/FlowRoot.vue")},
|
||||
|
||||
//Executions
|
||||
{name: "executions/list", path: "/:tenant?/executions", component: () => import("../components/executions/Executions.vue")},
|
||||
{
|
||||
name: "executions/list",
|
||||
path: "/:tenant?/executions",
|
||||
component: () => import("../components/executions/Executions.vue"),
|
||||
beforeEnter: (to, from, next) => {
|
||||
if (maybeAddTimeRangeFilter(to)) {
|
||||
next({
|
||||
name: to.name,
|
||||
params: to.params,
|
||||
query: to.query,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
},
|
||||
{name: "executions/update", path: "/:tenant?/executions/:namespace/:flowId/:id/:tab?", component: () => import("../components/executions/ExecutionRoot.vue")},
|
||||
|
||||
//TaskRuns
|
||||
@@ -69,7 +107,23 @@ export default [
|
||||
{name: "templates/update", path: "/:tenant?/templates/edit/:namespace/:id", component: () => import("../components/templates/TemplateEdit.vue")},
|
||||
|
||||
//Logs
|
||||
{name: "logs/list", path: "/:tenant?/logs", component: () => import("../components/logs/LogsWrapper.vue")},
|
||||
{
|
||||
name: "logs/list",
|
||||
path: "/:tenant?/logs",
|
||||
component: () => import("../components/logs/LogsWrapper.vue"),
|
||||
beforeEnter: (to, from, next) => {
|
||||
if (maybeAddTimeRangeFilter(to)) {
|
||||
next({
|
||||
name: to.name,
|
||||
params: to.params,
|
||||
query: to.query,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
},
|
||||
|
||||
//Namespaces
|
||||
{name: "namespaces/list", path: "/:tenant?/namespaces", component: () => import("override/components/namespaces/Namespaces.vue")},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {defineStore} from "pinia"
|
||||
import {trackFileOpen} from "../utils/tabTracking";
|
||||
import {useNamespacesStore} from "../override/stores/namespaces";
|
||||
|
||||
export interface EditorTabProps {
|
||||
name: string;
|
||||
@@ -24,14 +25,15 @@ export const useEditorStore = defineStore("editor", {
|
||||
}),
|
||||
actions: {
|
||||
saveAllTabs({namespace}: {namespace: string}) {
|
||||
const namespaceStore = useNamespacesStore();
|
||||
return Promise.all(
|
||||
this.tabs.map(async (tab) => {
|
||||
if(tab.flow) return;
|
||||
await this.vuexStore.dispatch("namespace/createFile", {
|
||||
if(tab.flow || !tab.content) return;
|
||||
await namespaceStore.createFile( {
|
||||
namespace,
|
||||
path: tab.path ?? tab.name,
|
||||
content: tab.content,
|
||||
}, {root: true});
|
||||
});
|
||||
this.setTabDirty({
|
||||
name: tab.name,
|
||||
path: tab.path,
|
||||
@@ -55,7 +57,7 @@ export const useEditorStore = defineStore("editor", {
|
||||
if (index === -1) {
|
||||
this.tabs.push({name, extension, persistent, path, flow});
|
||||
isDirty = false;
|
||||
|
||||
|
||||
if (path && !flow) {
|
||||
const fileName = name || path.split("/").pop() || "";
|
||||
trackFileOpen(fileName);
|
||||
|
||||
@@ -4,7 +4,7 @@ import permission from "../models/permission";
|
||||
import action from "../models/action";
|
||||
import * as YAML_UTILS from "@kestra-io/ui-libs/flow-yaml-utils";
|
||||
import Utils from "../utils/utils";
|
||||
import {editorViewTypes, storageKeys} from "../utils/constants";
|
||||
import {editorViewTypes} from "../utils/constants";
|
||||
import {apiUrl} from "override/utils/route";
|
||||
import {useCoreStore} from "./core";
|
||||
import {useEditorStore} from "./editor";
|
||||
@@ -18,6 +18,7 @@ import {transformResponse} from "../components/dependencies/composables/useDepen
|
||||
import {useNamespacesStore} from "override/stores/namespaces";
|
||||
import {useAuthStore} from "override/stores/auth";
|
||||
import {useRoute} from "vue-router";
|
||||
import {defaultNamespace} from "../composables/useNamespaces.ts";
|
||||
|
||||
const textYamlHeader = {
|
||||
headers: {
|
||||
@@ -134,8 +135,7 @@ export const useFlowStore = defineStore("flow", () => {
|
||||
const route = useRoute();
|
||||
|
||||
const getNamespace = () => {
|
||||
const defaultNamespace = localStorage.getItem(storageKeys.DEFAULT_NAMESPACE);
|
||||
return route.query.namespace || defaultNamespace || "company.team";
|
||||
return route.query.namespace || defaultNamespace();
|
||||
}
|
||||
|
||||
async function save({content, namespace}: { content?: string, namespace?: string }) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user