diff --git a/.env.example b/.env.example index bec7cae60..c55cfb433 100644 --- a/.env.example +++ b/.env.example @@ -36,6 +36,11 @@ VELA_API=http://localhost:8080 # default: 30 # VELA_MAX_BUILD_LIMIT= +# customize the max number of starlark exec steps that the UI will allow an admin to configure +# +# default: 99999 +# VELA_MAX_STARLARK_EXEC_LIMIT= + # customize the set of repos that are allowed to use schedules # # default: * diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d52e5229..9ac6c74ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: key: cypress-${{ runner.os }}-bin-v2-${{ hashFiles('**/package-lock.json') }} restore-keys: | cypress-${{ runner.os }}-bin-v2- - - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version-file: '.nvmrc' @@ -84,7 +84,7 @@ jobs: key: cypress-${{ runner.os }}-bin-v2-${{ hashFiles('**/package-lock.json') }} restore-keys: | cypress-${{ runner.os }}-bin-v2- - - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version-file: '.nvmrc' @@ -122,7 +122,7 @@ jobs: key: cypress-${{ runner.os }}-bin-v2-${{ hashFiles('**/package-lock.json') }} restore-keys: | cypress-${{ runner.os }}-bin-v2- - - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version-file: '.nvmrc' @@ -167,7 +167,7 @@ jobs: key: cypress-${{ runner.os }}-bin-v2-${{ hashFiles('**/package-lock.json') }} restore-keys: | cypress-${{ runner.os }}-bin-v2- - - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version-file: '.nvmrc' @@ -205,7 +205,7 @@ jobs: key: ${{ runner.os }}-modules-v2-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-modules-v2- - - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version-file: '.nvmrc' diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 317b4dd4b..b9b2b280a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -38,7 +38,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10 + uses: github/codeql-action/init@294a9d92911152fe08befb9ec03e240add280cb3 # v3.26.8 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -49,7 +49,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10 + uses: github/codeql-action/autobuild@294a9d92911152fe08befb9ec03e240add280cb3 # v3.26.8 # โ„น๏ธ Command-line programs to run using the OS shell. # ๐Ÿ“š https://git.io/JvXDl @@ -63,4 +63,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10 + uses: github/codeql-action/analyze@294a9d92911152fe08befb9ec03e240add280cb3 # v3.26.8 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 65d7a406c..8b887d598 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -35,7 +35,7 @@ jobs: key: ${{ runner.os }}-modules-v2-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-modules-v2- - - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version-file: '.nvmrc' diff --git a/.nvmrc b/.nvmrc index 907565957..3516580bb 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20.15.0 +20.17.0 diff --git a/Dockerfile b/Dockerfile index ecf8db32d..b784b9e5a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM fholzer/nginx-brotli:v1.24.0@sha256:55b6e7e04fa7eaf1bb0be210b9ea106292686deab6349a6efb9b52d229b0e940 +FROM fholzer/nginx-brotli:v1.26.2@sha256:54300ef5ddd64ea877bb363bf56c42a6f402089392b3b9b746e891193394b571 RUN apk update && \ apk add --no-cache ca-certificates && \ diff --git a/Dockerfile.local b/Dockerfile.local index d52975c90..007779c92 100644 --- a/Dockerfile.local +++ b/Dockerfile.local @@ -1,4 +1,4 @@ -FROM fholzer/nginx-brotli:v1.24.0@sha256:55b6e7e04fa7eaf1bb0be210b9ea106292686deab6349a6efb9b52d229b0e940 +FROM fholzer/nginx-brotli:v1.26.2@sha256:54300ef5ddd64ea877bb363bf56c42a6f402089392b3b9b746e891193394b571 RUN apk update && \ apk add --no-cache ca-certificates && \ diff --git a/cypress/fixtures/deployments_5.json b/cypress/fixtures/deployments_5.json new file mode 100644 index 000000000..f7bddddb5 --- /dev/null +++ b/cypress/fixtures/deployments_5.json @@ -0,0 +1,69 @@ +[ + { + "id": 5051, + "number": 1, + "repo_id": 1, + "url": "https://github.com/github/octocat.git/deployments/5051", + "user": "user", + "commit": "ce729516b279c7d7e66012387b39a8c13463fac5", + "ref": "refs/heads/main", + "task": "deploy:vela", + "target": "production", + "description": "Deployment request from Vela", + "payload": { + "foo": "bar" + } + }, + { + "id": 5052, + "number": 2, + "repo_id": 1, + "url": "https://github.com/github/octocat.git/deployments/5052", + "user": "user", + "commit": "ce729516b279c7d7e66012387b39a8c13463fac5", + "ref": "refs/heads/main", + "task": "deploy:vela", + "target": "production", + "description": "Deployment request from Vela", + "payload": {} + }, + { + "id": 5053, + "number": 3, + "repo_id": 1, + "url": "https://github.com/github/octocat.git/deployments/5053", + "user": "user", + "commit": "ce729516b279c7d7e66012387b39a8c13463fac5", + "ref": "refs/heads/main", + "task": "deploy:vela", + "target": "production", + "description": "Deployment request from Vela", + "payload": {} + }, + { + "id": 5054, + "number": 4, + "repo_id": 1, + "url": "https://github.com/github/octocat.git/deployments/5054", + "user": "user", + "commit": "ce729516b279c7d7e66012387b39a8c13463fac5", + "ref": "refs/heads/main", + "task": "deploy:vela", + "target": "production", + "description": "Deployment request from Vela", + "payload": {} + }, + { + "id": 5055, + "number": 5, + "repo_id": 1, + "url": "https://github.com/github/octocat.git/deployments/5055", + "user": "user", + "commit": "ce729516b279c7d7e66012387b39a8c13463fac5", + "ref": "refs/heads/main", + "task": "deploy:vela", + "target": "production", + "description": "Deployment request from Vela", + "payload": {} + } +] diff --git a/cypress/fixtures/services_5.json b/cypress/fixtures/services_5.json index 1722a510b..5c34d17fc 100644 --- a/cypress/fixtures/services_5.json +++ b/cypress/fixtures/services_5.json @@ -23,12 +23,12 @@ "number": 2, "name": "service-b", "stage": "", - "status": "success", + "status": "running", "error": "", "exit_code": 2, "created": 1572029883, "started": 1572029928, - "finished": 1572029935, + "finished": 0, "host": "", "runtime": "docker", "distribution": "linux" diff --git a/cypress/fixtures/steps_5.json b/cypress/fixtures/steps_5.json index 34ff91cf5..c64e5d5cf 100644 --- a/cypress/fixtures/steps_5.json +++ b/cypress/fixtures/steps_5.json @@ -23,12 +23,12 @@ "number": 2, "name": "build", "stage": "", - "status": "success", + "status": "running", "error": "", "exit_code": 2, "created": 1572029883, "started": 1572029928, - "finished": 1572029935, + "finished": 0, "host": "", "runtime": "docker", "distribution": "linux" diff --git a/cypress/fixtures/user_dashboards.json b/cypress/fixtures/user_dashboards.json new file mode 100644 index 000000000..98db5215d --- /dev/null +++ b/cypress/fixtures/user_dashboards.json @@ -0,0 +1,63 @@ +[ + { + "dashboard": { + "id": "6e26a6d0-2fc3-4531-a04d-678a58135288", + "name": "demo dashboard", + "created_at": 1726757028, + "created_by": "CookieCat", + "updated_at": 1726757028, + "updated_by": "CookieCat", + "admins": [ + { + "id": 1, + "name": "CookieCat", + "active": true + } + ], + "repos": [ + { + "id": 1, + "name": "github/repo1" + } + ] + }, + "repos": [ + { + "org": "github", + "name": "repo1", + "counter": 1, + "active": true, + "builds": [ + { + "number": 1, + "started": 1726757097, + "sender": "CookieCat", + "ref": "refs/heads/main", + "status": "running", + "event": "push", + "branch": "master", + "link": "http://vela.example.com/github/repo1/1" + } + ] + } + ] + }, + { + "dashboard": { + "id": "c4e8f563-4784-4b4b-9534-3007d579dc2a", + "name": "another demo dashboard", + "created_at": 1726757636, + "created_by": "CookieCat", + "updated_at": 1726757636, + "updated_by": "CookieCat", + "admins": [ + { + "id": 1, + "name": "CookieCat", + "active": true + } + ], + "repos": [] + } + } +] diff --git a/cypress/integration/dashboards.spec.js b/cypress/integration/dashboards.spec.js index 2ca8896b7..e9cf985a9 100644 --- a/cypress/integration/dashboards.spec.js +++ b/cypress/integration/dashboards.spec.js @@ -3,6 +3,56 @@ */ context('Dashboards', () => { + context('main dashboards page', () => { + beforeEach(() => { + cy.server(); + cy.route( + 'GET', + '*api/v1/user/dashboards', + 'fixture:user_dashboards.json', + ); + cy.login('/dashboards'); + }); + + it('shows the list of dashboards', () => { + cy.get('[data-test=dashboard-item]').should('have.length', 2); + }); + + it('shows the repos within a dashboard', () => { + cy.get('[data-test=dashboard-repos]').first().contains('github/repo1'); + }); + + it('shows a message when there are no repos', () => { + cy.get('[data-test=dashboard-repos]') + .eq(1) + .contains('No repositories in this dashboard'); + }); + + it('clicking dashoard name navigates to dashboard page', () => { + cy.get('[data-test=dashboard-item]') + .first() + .within(() => { + cy.get('a').first().click(); + cy.location('pathname').should( + 'eq', + '/dashboards/6e26a6d0-2fc3-4531-a04d-678a58135288', + ); + }); + }); + }); + + context('main dashboards page shows message', () => { + beforeEach(() => { + cy.server(); + cy.route( + 'GET', + '*api/v1/user/dashboards', + 'fixture:user_dashboards.json', + ); + cy.login('/dashboards'); + }); + }); + context('server returns dashboard with 3 cards, one without builds', () => { beforeEach(() => { cy.server(); @@ -112,15 +162,4 @@ context('Dashboards', () => { ); }); }); - - context('main dashboards page shows message', () => { - beforeEach(() => { - cy.server(); - cy.login('/dashboards'); - }); - - it('shows the welcome message', () => { - cy.get('[data-test=dashboards]').contains('Welcome to dashboards!'); - }); - }); }); diff --git a/cypress/integration/deployment.spec.js b/cypress/integration/deployment.spec.js index fbaddf174..a9c63b2f8 100644 --- a/cypress/integration/deployment.spec.js +++ b/cypress/integration/deployment.spec.js @@ -3,7 +3,7 @@ */ context('Deployment', () => { - context('server returning deployment', () => { + context('server returning deployments', () => { beforeEach(() => { cy.server(); cy.route( @@ -11,16 +11,33 @@ context('Deployment', () => { '*api/v1/deployments/github/octocat', 'fixture:deployment.json', ); - cy.login('/github/octocat/deployments/add'); + cy.route( + 'GET', + '*api/v1/deployments/github/octocat*', + 'fixture:deployments_5.json', + ); + cy.route('GET', '*api/v1/hooks/github/octocat*', []); + cy.route('GET', '*api/v1/user', 'fixture:user_admin.json'); + cy.route( + 'GET', + '*api/v1/repos/github/octocat', + 'fixture:repository.json', + ); + cy.route( + 'GET', + '*api/v1/repos/github/octocat/builds*', + 'fixture:builds_5.json', + ); }); - it('add parameter button should be disabled', () => { + cy.login('/github/octocat/deployments/add'); cy.get('[data-test=button-parameter-add]') .should('exist') .should('not.be.enabled') .contains('Add'); }); it('add parameter should work as intended', () => { + cy.login('/github/octocat/deployments/add'); cy.get('[data-test=parameters-list]') .should('exist') .children() @@ -50,5 +67,21 @@ context('Deployment', () => { .should('exist') .should('have.value', ''); }); + it('deployments table should show', () => { + cy.login('/github/octocat/deployments'); + cy.get('[data-test=deployments-table]').should('be.visible'); + }); + it('deployments table should contain deployments', () => { + cy.login('/github/octocat/deployments'); + cy.get('[data-test=deployments-row]') + .should('exist') + .contains('Deployment request from Vela'); + }); + it('deployments table should list of parameters', () => { + cy.login('/github/octocat/deployments'); + cy.get('[data-test=cell-list-item-parameters]') + .should('exist') + .contains('foo=bar'); + }); }); }); diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 201ef40d1..ba88fd95c 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -4,7 +4,7 @@ set -eu for f in /usr/share/nginx/html/*.js do # shellcheck disable=SC2016 - envsubst '${VELA_API},${VELA_DOCS_URL},${VELA_FEEDBACK_URL},${VELA_MAX_BUILD_LIMIT},${VELA_SCHEDULE_ALLOWLIST}' < "$f" > "$f".tmp && mv "$f".tmp "$f" + envsubst '${VELA_API},${VELA_DOCS_URL},${VELA_FEEDBACK_URL},${VELA_MAX_BUILD_LIMIT},${VELA_MAX_STARLARK_EXEC_LIMIT},${VELA_SCHEDULE_ALLOWLIST}' < "$f" > "$f".tmp && mv "$f".tmp "$f" done NGINX_CONF=/etc/nginx/conf.d/default.conf diff --git a/elm.json b/elm.json index 1d57a99d9..401a1c798 100644 --- a/elm.json +++ b/elm.json @@ -34,7 +34,7 @@ "elm/random": "1.0.0", "elm/regex": "1.0.0", "elm/virtual-dom": "1.0.3", - "elm-community/intdict": "3.0.0", + "elm-community/intdict": "3.1.0", "myrho/elm-round": "1.0.5", "rtfeldman/elm-iso8601-date-strings": "1.1.4" } diff --git a/package-lock.json b/package-lock.json index b5e605a6f..f37b4a648 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "license": "Apache-2.0", "dependencies": { - "@hpcc-js/wasm": "^2.13.0", + "@hpcc-js/wasm-graphviz": "^1.3.0", "clipboard": "2.0.11", "d3": "^7.8.5" }, @@ -18,7 +18,7 @@ "@fullhuman/postcss-purgecss": "6.0.0", "@parcel/transformer-elm": "2.12.0", "@parcel/transformer-sass": "2.12.0", - "axe-core": "4.9.1", + "axe-core": "4.10.0", "cypress": "5.6.0", "cypress-axe": "0.14.0", "elm": "0.19.1-6", @@ -27,18 +27,18 @@ "make-dir-cli": "4.0.0", "ncp": "2.0.0", "parcel": "2.12.0", - "postcss": "8.4.38", - "prettier": "3.3.2", - "rimraf": "5.0.7", - "start-server-and-test": "2.0.4", - "stylelint": "16.6.1", + "postcss": "8.4.47", + "prettier": "3.3.3", + "rimraf": "5.0.10", + "start-server-and-test": "2.0.8", + "stylelint": "16.9.0", "stylelint-color-format": "1.1.0", - "stylelint-config-recommended-scss": "14.0.0", + "stylelint-config-recommended-scss": "14.1.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", - "stylelint-declaration-strict-value": "1.10.4", + "stylelint-declaration-strict-value": "1.10.6", "stylelint-high-performance-animation": "1.10.0", "stylelint-order": "6.0.4", - "stylelint-scss": "6.3.2" + "stylelint-scss": "6.7.0" }, "engines": { "node": ">=18.12.0" @@ -279,77 +279,10 @@ "node": ">=0.1.90" } }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.3.tgz", - "integrity": "sha512-xI/tL2zxzEbESvnSxwFgwvy5HS00oCXxL4MLs6HUiDcYfwowsoQaABKxUElp1ARITrINzBnsECOc1q0eg2GOrA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^2.3.1" - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.3.1.tgz", - "integrity": "sha512-iMNHTyxLbBlWIfGtabT157LH9DUx9X8+Y3oymFEuMj8HNc+rpE3dPFGFgHjpKfjeFDjLjYIAIhXPGvS2lKxL9g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18" - } - }, - "node_modules/@csstools/media-query-list-parser": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.11.tgz", - "integrity": "sha512-uox5MVhvNHqitPP+SynrB1o8oPxPMt2JLgp5ghJOWf54WGQ5OKu47efne49r1SWqs3wRP8xSWjnO9MBKxhB1dA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1" - } - }, "node_modules/@csstools/selector-specificity": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.1.1.tgz", - "integrity": "sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-4.0.0.tgz", + "integrity": "sha512-189nelqtPd8++phaHNwYovKZI0FOzH1vQEE3QhHHkNIGrg5fSs9CbYP3RvfEH5geztnIA9Jwq91wyOIwAW5JIQ==", "dev": true, "funding": [ { @@ -363,10 +296,10 @@ ], "license": "MIT-0", "engines": { - "node": "^14 || ^16 || >=18" + "node": ">=18" }, "peerDependencies": { - "postcss-selector-parser": "^6.0.13" + "postcss-selector-parser": "^6.1.0" } }, "node_modules/@cypress/listr-verbose-renderer": { @@ -621,28 +554,24 @@ "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@hapi/topo": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^9.0.0" } }, - "node_modules/@hpcc-js/wasm": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@hpcc-js/wasm/-/wasm-2.16.2.tgz", - "integrity": "sha512-THiidUMYR8/cIfFT3MVcWuRE7bQKh295nrFBxGvUNc4Nq8e2uU1LtiplHs7AUkJ0GxgvZoR+8TQ1/E3Qb/uE2g==", - "license": "Apache-2.0", - "dependencies": { - "yargs": "17.7.2" - }, - "bin": { - "dot-wasm": "bin/dot-wasm.js" - } + "node_modules/@hpcc-js/wasm-graphviz": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@hpcc-js/wasm-graphviz/-/wasm-graphviz-1.5.0.tgz", + "integrity": "sha512-qPPcmD7SpMZL2Eb5I0pAl+sm+bdGSMjN+0iqcKTJlPqwEBlwtuc4gao56gkmXsJd3k6lPYTZdrG3wbsUeGRdKg==", + "license": "Apache-2.0" }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -2611,6 +2540,7 @@ "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^9.0.0" } @@ -2619,13 +2549,15 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@sideway/pinpoint": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@swc/core": { "version": "1.4.2", @@ -2923,6 +2855,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -3060,9 +2993,9 @@ "dev": true }, "node_modules/axe-core": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.9.1.tgz", - "integrity": "sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.0.tgz", + "integrity": "sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g==", "dev": true, "license": "MPL-2.0", "engines": { @@ -3070,12 +3003,13 @@ } }, "node_modules/axios": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", - "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dev": true, + "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.4", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -3085,6 +3019,7 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -3252,9 +3187,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001593", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001593.tgz", - "integrity": "sha512-UWM1zlo3cZfkpBysd7AS+z+v007q9G1+fLTUU42rQnY6t2axoogPW/xol6T7juU5EUoOhML4WgBIdG+9yYqAjQ==", + "version": "1.0.30001662", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001662.tgz", + "integrity": "sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA==", "dev": true, "funding": [ { @@ -3417,54 +3352,6 @@ "tiny-emitter": "^2.0.0" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -3497,6 +3384,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -3507,7 +3395,8 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/color-string": { "version": "1.9.1", @@ -4273,13 +4162,13 @@ "dev": true }, "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -4553,7 +4442,8 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "node_modules/end-of-stream": { "version": "1.4.4", @@ -4619,6 +4509,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, "engines": { "node": ">=6" } @@ -4889,9 +4780,9 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "dev": true, "funding": [ { @@ -4899,6 +4790,7 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -5009,14 +4901,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -5423,10 +5307,11 @@ } }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -5544,6 +5429,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "engines": { "node": ">=8" } @@ -5695,10 +5581,11 @@ } }, "node_modules/joi": { - "version": "17.12.2", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.2.tgz", - "integrity": "sha512-RonXAIzCiHLc8ss3Ibuz45u28GOsWE1UpfDXLbN/9NKbL4tCJf8TWYVKsoYuuh+sAUt7fsSNpA+r2+TBA6Wjmw==", + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", @@ -5821,9 +5708,9 @@ } }, "node_modules/known-css-properties": { - "version": "0.31.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.31.0.tgz", - "integrity": "sha512-sBPIUGTNF0czz0mwGGUoKKJC8Q7On1GPbCSFPfyEsfHb2DyBG0Y4QtV+EVWpINSaiGKZblDNuF5AezxSgOhesQ==", + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.34.0.tgz", + "integrity": "sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==", "dev": true, "license": "MIT" }, @@ -6511,9 +6398,9 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { @@ -6607,10 +6494,11 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, "node_modules/msgpackr": { "version": "1.10.1", @@ -6919,6 +6807,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parcel": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/parcel/-/parcel-2.12.0.tgz", @@ -7065,9 +6960,9 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", "dev": true, "license": "ISC" }, @@ -7093,9 +6988,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -7111,10 +7006,11 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -7127,10 +7023,11 @@ "dev": true }, "node_modules/postcss-resolve-nested-selector": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", - "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==", - "dev": true + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz", + "integrity": "sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==", + "dev": true, + "license": "MIT" }, "node_modules/postcss-safe-parser": { "version": "7.0.0", @@ -7185,9 +7082,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", - "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, "license": "MIT", "dependencies": { @@ -7263,9 +7160,9 @@ } }, "node_modules/prettier": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", - "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, "license": "MIT", "bin": { @@ -7300,7 +7197,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ps-tree": { "version": "1.2.0", @@ -7480,14 +7378,6 @@ "throttleit": "^1.0.0" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -7545,9 +7435,9 @@ } }, "node_modules/rimraf": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.7.tgz", - "integrity": "sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", "dev": true, "license": "ISC", "dependencies": { @@ -7556,17 +7446,14 @@ "bin": { "rimraf": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=14.18" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/rimraf/node_modules/glob": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", - "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "license": "ISC", "dependencies": { @@ -7574,30 +7461,25 @@ "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/rimraf/node_modules/jackspeak": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", - "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">=14" - }, "funding": { "url": "https://github.com/sponsors/isaacs" }, @@ -7606,9 +7488,9 @@ } }, "node_modules/rimraf/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { @@ -7839,10 +7721,11 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -7914,20 +7797,20 @@ "dev": true }, "node_modules/start-server-and-test": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-2.0.4.tgz", - "integrity": "sha512-CKNeBTcP0hVqIlNismHMudb9q3lLdAjcVPO13/7gfI66fcJpeIb/o4NzQd1JK/CD+lfWVqr10ZH9Y14+OwlJuw==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-2.0.8.tgz", + "integrity": "sha512-v2fV6NV2F7tL1ocwfI4Wpait+IKjRbT5l3ZZ+ZikXdMLmxYsS8ynGAsCQAUVXkVyGyS+UibsRnvgHkMvJIvCsw==", "dev": true, "license": "MIT", "dependencies": { "arg": "^5.0.2", "bluebird": "3.7.2", "check-more-types": "2.24.0", - "debug": "4.3.5", + "debug": "4.3.7", "execa": "5.1.1", "lazy-ass": "1.6.0", "ps-tree": "1.2.0", - "wait-on": "7.2.0" + "wait-on": "8.0.1" }, "bin": { "server-test": "src/bin/start.js", @@ -8013,6 +7896,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -8062,6 +7946,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "engines": { "node": ">=8" } @@ -8070,6 +7955,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -8127,9 +8013,9 @@ "dev": true }, "node_modules/stylelint": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.6.1.tgz", - "integrity": "sha512-yNgz2PqWLkhH2hw6X9AweV9YvoafbAD5ZsFdKN9BvSDVwGvPh+AUIrn7lYwy1S7IHmtFin75LLfX1m0D2tHu8Q==", + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.9.0.tgz", + "integrity": "sha512-31Nm3WjxGOBGpQqF43o3wO9L5AC36TPIe6030Lnm13H3vDMTcS21DrLh69bMX+DBilKqMMVLian4iG6ybBoNRQ==", "dev": true, "funding": [ { @@ -8143,17 +8029,17 @@ ], "license": "MIT", "dependencies": { - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1", - "@csstools/media-query-list-parser": "^2.1.11", - "@csstools/selector-specificity": "^3.1.1", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", + "@csstools/media-query-list-parser": "^3.0.1", + "@csstools/selector-specificity": "^4.0.0", "@dual-bundle/import-meta-resolve": "^4.1.0", "balanced-match": "^2.0.0", "colord": "^2.9.3", "cosmiconfig": "^9.0.0", "css-functions-list": "^3.2.2", "css-tree": "^2.3.1", - "debug": "^4.3.4", + "debug": "^4.3.6", "fast-glob": "^3.3.2", "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^9.0.0", @@ -8161,24 +8047,24 @@ "globby": "^11.1.0", "globjoin": "^0.1.4", "html-tags": "^3.3.1", - "ignore": "^5.3.1", + "ignore": "^5.3.2", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", - "known-css-properties": "^0.31.0", + "known-css-properties": "^0.34.0", "mathml-tag-names": "^2.1.3", "meow": "^13.2.0", - "micromatch": "^4.0.7", + "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.0.1", - "postcss": "^8.4.38", - "postcss-resolve-nested-selector": "^0.1.1", + "postcss": "^8.4.41", + "postcss-resolve-nested-selector": "^0.1.6", "postcss-safe-parser": "^7.0.0", - "postcss-selector-parser": "^6.1.0", + "postcss-selector-parser": "^6.1.2", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", "strip-ansi": "^7.1.0", - "supports-hyperlinks": "^3.0.0", + "supports-hyperlinks": "^3.1.0", "svg-tags": "^1.0.0", "table": "^6.8.2", "write-file-atomic": "^5.0.1" @@ -8207,33 +8093,45 @@ } }, "node_modules/stylelint-config-recommended": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-14.0.0.tgz", - "integrity": "sha512-jSkx290CglS8StmrLp2TxAppIajzIBZKYm3IxT89Kg6fGlxbPiTiyH9PS5YUuVAFwaJLl1ikiXX0QWjI0jmgZQ==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-14.0.1.tgz", + "integrity": "sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", "engines": { "node": ">=18.12.0" }, "peerDependencies": { - "stylelint": "^16.0.0" + "stylelint": "^16.1.0" } }, "node_modules/stylelint-config-recommended-scss": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-14.0.0.tgz", - "integrity": "sha512-HDvpoOAQ1RpF+sPbDOT2Q2/YrBDEJDnUymmVmZ7mMCeNiFSdhRdyGEimBkz06wsN+HaFwUh249gDR+I9JR7Onw==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-14.1.0.tgz", + "integrity": "sha512-bhaMhh1u5dQqSsf6ri2GVWWQW5iUjBYgcHkh7SgDDn92ijoItC/cfO/W+fpXshgTQWhwFkP1rVcewcv4jaftRg==", "dev": true, + "license": "MIT", "dependencies": { "postcss-scss": "^4.0.9", - "stylelint-config-recommended": "^14.0.0", - "stylelint-scss": "^6.0.0" + "stylelint-config-recommended": "^14.0.1", + "stylelint-scss": "^6.4.0" }, "engines": { "node": ">=18.12.0" }, "peerDependencies": { "postcss": "^8.3.3", - "stylelint": "^16.0.2" + "stylelint": "^16.6.1" }, "peerDependenciesMeta": { "postcss": { @@ -8254,10 +8152,11 @@ } }, "node_modules/stylelint-declaration-strict-value": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/stylelint-declaration-strict-value/-/stylelint-declaration-strict-value-1.10.4.tgz", - "integrity": "sha512-unOEftKCOb78Zr+WStqyVj9V1rCdUo+PJI3vFPiHPdu+O9o71K9Mu+txc6VDF7gBXyTTMHbbjIvHk3VNzuixzQ==", + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/stylelint-declaration-strict-value/-/stylelint-declaration-strict-value-1.10.6.tgz", + "integrity": "sha512-aZGEW4Ee26Tx4UvpQJbcElVXZ42EleujEByiyKDTT7t83EeSe9t0lAG3OOLJnnvLjz/dQnp+L+3IYTMeQI51vQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18.12.0" }, @@ -8291,16 +8190,18 @@ } }, "node_modules/stylelint-scss": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.3.2.tgz", - "integrity": "sha512-pNk9mXOVKkQtd+SROPC9io8ISSgX+tOVPhFdBE+LaKQnJMLdWPbGKAGYv4Wmf/RrnOjkutunNTN9kKMhkdE5qA==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.7.0.tgz", + "integrity": "sha512-RFIa2A+pVWS5wjNT+whtK7wsbZEWazyqesCuSaPbPlZ8lh2TujwVJSnCYJijg6ChZzwI8pZPRZS1L6A9aCbXDg==", "dev": true, "license": "MIT", "dependencies": { - "known-css-properties": "^0.31.0", + "css-tree": "2.3.1", + "is-plain-object": "5.0.0", + "known-css-properties": "^0.34.0", "postcss-media-query-parser": "^0.2.3", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-selector-parser": "^6.1.0", + "postcss-resolve-nested-selector": "^0.1.6", + "postcss-selector-parser": "^6.1.2", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -8310,6 +8211,73 @@ "stylelint": "^16.0.2" } }, + "node_modules/stylelint/node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.1.tgz", + "integrity": "sha512-lSquqZCHxDfuTg/Sk2hiS0mcSFCEBuj49JfzPHJogDBT0mGCyY5A1AQzBWngitrp7i1/HAZpIgzF/VjhOEIJIg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.1" + } + }, + "node_modules/stylelint/node_modules/@csstools/css-tokenizer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.1.tgz", + "integrity": "sha512-UBqaiu7kU0lfvaP982/o3khfXccVlHPWp0/vwwiIgDF0GmqqqxoiXC/6FCjlS9u92f7CoEz6nXKQnrn1kIAkOw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/stylelint/node_modules/@csstools/media-query-list-parser": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-3.0.1.tgz", + "integrity": "sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1" + } + }, "node_modules/stylelint/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -8396,16 +8364,20 @@ } }, "node_modules/supports-hyperlinks": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", - "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz", + "integrity": "sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" }, "engines": { "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/svg-tags": { @@ -8864,13 +8836,14 @@ "dev": true }, "node_modules/wait-on": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", - "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-8.0.1.tgz", + "integrity": "sha512-1wWQOyR2LVVtaqrcIL2+OM+x7bkpmzVROa0Nf6FryXkS+er5Sa1kzFGjzZRqLnHa3n1rACFLeTwUqE1ETL9Mig==", "dev": true, + "license": "MIT", "dependencies": { - "axios": "^1.6.1", - "joi": "^17.11.0", + "axios": "^1.7.7", + "joi": "^17.13.3", "lodash": "^4.17.21", "minimist": "^1.2.8", "rxjs": "^7.8.1" @@ -9047,45 +9020,12 @@ "node": ">=8.0" } }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } - }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", diff --git a/package.json b/package.json index 1f55b5359..abd5da791 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "node": ">=18.12.0" }, "dependencies": { - "@hpcc-js/wasm": "^2.13.0", + "@hpcc-js/wasm-graphviz": "^1.3.0", "clipboard": "2.0.11", "d3": "^7.8.5" }, @@ -16,7 +16,7 @@ "@fullhuman/postcss-purgecss": "6.0.0", "@parcel/transformer-elm": "2.12.0", "@parcel/transformer-sass": "2.12.0", - "axe-core": "4.9.1", + "axe-core": "4.10.0", "cypress": "5.6.0", "cypress-axe": "0.14.0", "elm": "0.19.1-6", @@ -25,18 +25,18 @@ "make-dir-cli": "4.0.0", "ncp": "2.0.0", "parcel": "2.12.0", - "postcss": "8.4.38", - "prettier": "3.3.2", - "rimraf": "5.0.7", - "start-server-and-test": "2.0.4", - "stylelint": "16.6.1", + "postcss": "8.4.47", + "prettier": "3.3.3", + "rimraf": "5.0.10", + "start-server-and-test": "2.0.8", + "stylelint": "16.9.0", "stylelint-color-format": "1.1.0", - "stylelint-config-recommended-scss": "14.0.0", + "stylelint-config-recommended-scss": "14.1.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", - "stylelint-declaration-strict-value": "1.10.4", + "stylelint-declaration-strict-value": "1.10.6", "stylelint-high-performance-animation": "1.10.0", "stylelint-order": "6.0.4", - "stylelint-scss": "6.3.2" + "stylelint-scss": "6.7.0" }, "scripts": { "predev": "npm run clean", diff --git a/src/elm/Api/Endpoint.elm b/src/elm/Api/Endpoint.elm index 225bd70ea..581a71c81 100644 --- a/src/elm/Api/Endpoint.elm +++ b/src/elm/Api/Endpoint.elm @@ -26,6 +26,7 @@ type Endpoint | Logout | CurrentUser | Dashboard String + | Dashboards | Deployment Vela.Org Vela.Repo (Maybe String) | Deployments (Maybe Pagination.Page) (Maybe Pagination.PerPage) Vela.Org Vela.Repo | Token @@ -164,6 +165,9 @@ toUrl api endpoint = Deployments maybePage maybePerPage org repo -> url api [ "deployments", org, repo ] <| Pagination.toQueryParams maybePage maybePerPage + Dashboards -> + url api [ "user", "dashboards" ] [] + Dashboard dashboard -> url api [ "dashboards", dashboard ] [] diff --git a/src/elm/Api/Operations.elm b/src/elm/Api/Operations.elm index 96d62acb5..a807775c6 100644 --- a/src/elm/Api/Operations.elm +++ b/src/elm/Api/Operations.elm @@ -20,6 +20,8 @@ module Api.Operations exposing , enableRepo , expandPipelineConfig , finishAuthentication + , getAllBuildServices + , getAllBuildSteps , getBuild , getBuildGraph , getBuildServiceLog @@ -28,6 +30,7 @@ module Api.Operations exposing , getBuildSteps , getCurrentUser , getDashboard + , getDashboards , getOrgBuilds , getOrgRepos , getOrgSecret @@ -681,6 +684,31 @@ getBuildSteps baseUrl session options = |> withAuth session +{-| getAllBuildSteps : retrieves all steps for a build. +-} +getAllBuildSteps : + String + -> Session + -> + { a + | org : String + , repo : String + , build : String + } + -> Request Vela.Step +getAllBuildSteps baseUrl session options = + get baseUrl + (Api.Endpoint.Steps + (Just 1) + (Just 100) + options.org + options.repo + options.build + ) + Vela.decodeStep + |> withAuth session + + {-| getBuildServices : retrieves services for a build. -} getBuildServices : @@ -708,6 +736,31 @@ getBuildServices baseUrl session options = |> withAuth session +{-| getAllBuildServices : retrieves all services for a build. +-} +getAllBuildServices : + String + -> Session + -> + { a + | org : String + , repo : String + , build : String + } + -> Request Vela.Service +getAllBuildServices baseUrl session options = + get baseUrl + (Api.Endpoint.Services + (Just 1) + (Just 100) + options.org + options.repo + options.build + ) + Vela.decodeService + |> withAuth session + + {-| getBuildStepLog : retrieves a log for a step. -} getBuildStepLog : @@ -1264,3 +1317,16 @@ getDashboard baseUrl session options = ) Vela.decodeDashboard |> withAuth session + + +{-| getDashboards : retrieves the dashboards for the current user. +-} +getDashboards : + String + -> Session + -> Request (List Vela.Dashboard) +getDashboards baseUrl session = + get baseUrl + Api.Endpoint.Dashboards + Vela.decodeDashboards + |> withAuth session diff --git a/src/elm/Components/Help.elm b/src/elm/Components/Help.elm index 042e4b838..867890d83 100644 --- a/src/elm/Components/Help.elm +++ b/src/elm/Components/Help.elm @@ -54,6 +54,7 @@ view shared props = [ summary [ class "summary" , class "-no-pad" + , attribute "aria-label" "show/hide contextual CLI tips" , Util.testAttribute "help-trigger" , tabindex 0 , Util.onClickPreventDefault (props.showHide Nothing) diff --git a/src/elm/Effect.elm b/src/elm/Effect.elm index 7b8817a13..0e1678869 100644 --- a/src/elm/Effect.elm +++ b/src/elm/Effect.elm @@ -9,7 +9,7 @@ module Effect exposing , sendCmd, sendMsg , pushRoute, replaceRoute, loadExternalUrl , map, toCmd - , addAlertError, addAlertSuccess, addDeployment, addFavorites, addOrgSecret, addRepoSchedule, addRepoSecret, addSharedSecret, alertsUpdate, approveBuild, cancelBuild, chownRepo, clearRedirect, deleteOrgSecret, deleteRepoSchedule, deleteRepoSecret, deleteSharedSecret, disableRepo, downloadFile, enableRepo, expandPipelineConfig, finishAuthentication, focusOn, getBuild, getBuildGraph, getBuildServiceLog, getBuildServices, getBuildStepLog, getBuildSteps, getCurrentUser, getCurrentUserShared, getDashboard, getOrgBuilds, getOrgRepos, getOrgSecret, getOrgSecrets, getPipelineConfig, getPipelineTemplates, getRepo, getRepoBuilds, getRepoBuildsShared, getRepoDeployments, getRepoHooks, getRepoHooksShared, getRepoSchedule, getRepoSchedules, getRepoSecret, getRepoSecrets, getSettings, getSharedSecret, getSharedSecrets, getWorkers, handleHttpError, logout, pushPath, redeliverHook, repairRepo, replacePath, replaceRouteRemoveTabHistorySkipDomFocus, restartBuild, setRedirect, setTheme, updateFavicon, updateFavorite, updateOrgSecret, updateRepo, updateRepoSchedule, updateRepoSecret, updateSettings, updateSharedSecret, updateSourceReposShared + , addAlertError, addAlertSuccess, addDeployment, addFavorites, addOrgSecret, addRepoSchedule, addRepoSecret, addSharedSecret, alertsUpdate, approveBuild, cancelBuild, chownRepo, clearRedirect, deleteOrgSecret, deleteRepoSchedule, deleteRepoSecret, deleteSharedSecret, disableRepo, downloadFile, enableRepo, expandPipelineConfig, finishAuthentication, focusOn, getAllBuildServices, getAllBuildSteps, getBuild, getBuildGraph, getBuildServiceLog, getBuildServices, getBuildStepLog, getBuildSteps, getCurrentUser, getCurrentUserShared, getDashboard, getDashboards, getOrgBuilds, getOrgRepos, getOrgSecret, getOrgSecrets, getPipelineConfig, getPipelineTemplates, getRepo, getRepoBuilds, getRepoBuildsShared, getRepoDeployments, getRepoHooks, getRepoHooksShared, getRepoSchedule, getRepoSchedules, getRepoSecret, getRepoSecrets, getSettings, getSharedSecret, getSharedSecrets, getWorkers, handleHttpError, logout, pushPath, redeliverHook, repairRepo, replacePath, replaceRouteRemoveTabHistorySkipDomFocus, restartBuild, setRedirect, setTheme, updateFavicon, updateFavorite, updateOrgSecret, updateRepo, updateRepoHooksShared, updateRepoSchedule, updateRepoSecret, updateSettings, updateSharedSecret, updateSourceReposShared ) {-| @@ -626,6 +626,11 @@ getRepoHooksShared options = SendSharedMsg <| Shared.Msg.GetRepoHooks options +updateRepoHooksShared : { hooks : WebData (List Vela.Hook) } -> Effect msg +updateRepoHooksShared options = + SendSharedMsg <| Shared.Msg.UpdateRepoHooks options + + redeliverHook : { baseUrl : String , session : Auth.Session.Session @@ -791,6 +796,26 @@ getBuildSteps options = |> sendCmd +getAllBuildSteps : + { baseUrl : String + , session : Auth.Session.Session + , onResponse : Result (Http.Detailed.Error String) ( Http.Metadata, List Vela.Step ) -> msg + , org : String + , repo : String + , build : String + } + -> Effect msg +getAllBuildSteps options = + Api.tryAll + options.onResponse + (Api.Operations.getAllBuildSteps + options.baseUrl + options.session + options + ) + |> sendCmd + + getBuildServices : { baseUrl : String , session : Auth.Session.Session @@ -813,6 +838,26 @@ getBuildServices options = |> sendCmd +getAllBuildServices : + { baseUrl : String + , session : Auth.Session.Session + , onResponse : Result (Http.Detailed.Error String) ( Http.Metadata, List Vela.Service ) -> msg + , org : String + , repo : String + , build : String + } + -> Effect msg +getAllBuildServices options = + Api.tryAll + options.onResponse + (Api.Operations.getAllBuildServices + options.baseUrl + options.session + options + ) + |> sendCmd + + getBuildStepLog : { baseUrl : String , session : Auth.Session.Session @@ -1339,3 +1384,19 @@ getDashboard options = options ) |> sendCmd + + +getDashboards : + { baseUrl : String + , session : Auth.Session.Session + , onResponse : Result (Http.Detailed.Error String) ( Http.Metadata, List Vela.Dashboard ) -> msg + } + -> Effect msg +getDashboards options = + Api.try + options.onResponse + (Api.Operations.getDashboards + options.baseUrl + options.session + ) + |> sendCmd diff --git a/src/elm/Layouts/Default/Build.elm b/src/elm/Layouts/Default/Build.elm index e4adaf2d0..9c1aef9bd 100644 --- a/src/elm/Layouts/Default/Build.elm +++ b/src/elm/Layouts/Default/Build.elm @@ -328,24 +328,42 @@ update props shared route msg model = -- REFRESH Tick options -> + let + isBuildRunning = + case model.build of + RemoteData.Success build -> + build.finished == 0 + + _ -> + True + + getRepoBuildsEffect = + Effect.getRepoBuildsShared + { pageNumber = Nothing + , perPage = Nothing + , maybeEvent = Nothing + , org = props.org + , repo = props.repo + } + + runEffects = + if isBuildRunning then + [ getRepoBuildsEffect + , Effect.getBuild + { baseUrl = shared.velaAPIBaseURL + , session = shared.session + , onResponse = GetBuildResponse + , org = props.org + , repo = props.repo + , build = props.build + } + ] + + else + [ getRepoBuildsEffect ] + in ( model - , Effect.batch - [ Effect.getRepoBuildsShared - { pageNumber = Nothing - , perPage = Nothing - , maybeEvent = Nothing - , org = props.org - , repo = props.repo - } - , Effect.getBuild - { baseUrl = shared.velaAPIBaseURL - , session = shared.session - , onResponse = GetBuildResponse - , org = props.org - , repo = props.repo - , build = props.build - } - ] + , Effect.batch runEffects ) diff --git a/src/elm/Layouts/Default/Org.elm b/src/elm/Layouts/Default/Org.elm index 933f5a370..44f5f2c39 100644 --- a/src/elm/Layouts/Default/Org.elm +++ b/src/elm/Layouts/Default/Org.elm @@ -135,6 +135,13 @@ view props shared route { toContentMsg, model, content } = { buttons = props.navButtons ++ [ a + [ class "button" + , class "-outline" + , Util.testAttribute "dashboards-button" + , Route.Path.href Route.Path.Dashboards + ] + [ text "Dashboards" ] + , a [ class "button" , class "-outline" , Util.testAttribute "source-repos" diff --git a/src/elm/Layouts/Default/Repo.elm b/src/elm/Layouts/Default/Repo.elm index 753d7b4ed..3727ff4ad 100644 --- a/src/elm/Layouts/Default/Repo.elm +++ b/src/elm/Layouts/Default/Repo.elm @@ -148,24 +148,27 @@ update props route msg model = -- REFRESH Tick options -> + let + -- the hooks page has its own refresh call for hooks; + -- this is to prevent double calls + isNotOnHooksPage = + route.path /= Route.Path.Org__Repo__Hooks { org = props.org, repo = props.repo } + + runEffect = + if isNotOnHooksPage then + Effect.getRepoHooksShared + { pageNumber = Nothing + , perPage = Nothing + , maybeEvent = Nothing + , org = props.org + , repo = props.repo + } + + else + Effect.none + in ( model - , Effect.batch - [ Effect.getCurrentUserShared {} - , Effect.getRepoBuildsShared - { pageNumber = Nothing - , perPage = Nothing - , maybeEvent = Nothing - , org = props.org - , repo = props.repo - } - , Effect.getRepoHooksShared - { pageNumber = Nothing - , perPage = Nothing - , maybeEvent = Nothing - , org = props.org - , repo = props.repo - } - ] + , runEffect ) diff --git a/src/elm/Pages/Admin/Settings.elm b/src/elm/Pages/Admin/Settings.elm index 2b285c5c2..04d4bcc43 100644 --- a/src/elm/Pages/Admin/Settings.elm +++ b/src/elm/Pages/Admin/Settings.elm @@ -876,14 +876,14 @@ view shared route model = [ viewFieldHeader "Starlark Exec Limit" , viewFieldDescription "The number of executions allowed for Starlark scripts." , viewFieldEnvKeyValue "VELA_COMPILER_STARLARK_EXEC_LIMIT" - , viewFieldLimits <| text <| numberBoundsToString starlarkExecLimitMin starlarkExecLimitMax + , viewFieldLimits <| text <| numberBoundsToString starlarkExecLimitMin <| starlarkExecLimitMax shared , div [ class "form-controls" ] [ Components.Form.viewNumberInput { title = Nothing , subtitle = Nothing , id_ = starlarkExecLimitHtmlId , val = model.starlarkExecLimitIn - , placeholder_ = numberBoundsToString starlarkExecLimitMin starlarkExecLimitMax + , placeholder_ = numberBoundsToString starlarkExecLimitMin <| starlarkExecLimitMax shared , wrapperClassList = [ ( "-wide", True ) ] , classList_ = [] , rows_ = Nothing @@ -891,7 +891,7 @@ view shared route model = , msg = StarlarkExecLimitOnInput , disabled_ = False , min = Just starlarkExecLimitMin - , max = Just starlarkExecLimitMax + , max = Just <| starlarkExecLimitMax shared } , Components.Form.viewButton { id_ = starlarkExecLimitHtmlId ++ "-update" @@ -908,7 +908,7 @@ view shared route model = limit == s.compiler.starlarkExecLimit || (limit < starlarkExecLimitMin) - || (limit > starlarkExecLimitMax) + || (limit > starlarkExecLimitMax shared) Nothing -> True @@ -1269,6 +1269,6 @@ starlarkExecLimitMin = {-| starlarkExecLimitMax : returns the maximum value for the starlark exec limit -} -starlarkExecLimitMax : Int -starlarkExecLimitMax = - 9999 +starlarkExecLimitMax : Shared.Model -> Int +starlarkExecLimitMax shared = + shared.velaMaxStarlarkExecLimit diff --git a/src/elm/Pages/Dash/Secrets/Engine_/Org/Org_.elm b/src/elm/Pages/Dash/Secrets/Engine_/Org/Org_.elm index 12dc70499..d563a157c 100644 --- a/src/elm/Pages/Dash/Secrets/Engine_/Org/Org_.elm +++ b/src/elm/Pages/Dash/Secrets/Engine_/Org/Org_.elm @@ -216,29 +216,7 @@ update shared route msg model = -- REFRESH Tick options -> - ( model - , Effect.batch - [ Effect.getOrgSecrets - { baseUrl = shared.velaAPIBaseURL - , session = shared.session - , onResponse = GetOrgSecretsResponse - , pageNumber = Dict.get "page" route.query |> Maybe.andThen String.toInt - , perPage = Dict.get "perPage" route.query |> Maybe.andThen String.toInt - , engine = route.params.engine - , org = route.params.org - } - , Effect.getSharedSecrets - { baseUrl = shared.velaAPIBaseURL - , session = shared.session - , onResponse = GetSharedSecretsResponse - , pageNumber = Nothing - , perPage = Nothing - , engine = route.params.engine - , org = route.params.org - , team = "*" - } - ] - ) + ( model, Effect.none ) diff --git a/src/elm/Pages/Dash/Secrets/Engine_/Org/Org_/Name_.elm b/src/elm/Pages/Dash/Secrets/Engine_/Org/Org_/Name_.elm index c4c9ae28a..ac8ee310f 100644 --- a/src/elm/Pages/Dash/Secrets/Engine_/Org/Org_/Name_.elm +++ b/src/elm/Pages/Dash/Secrets/Engine_/Org/Org_/Name_.elm @@ -173,7 +173,10 @@ update shared route msg model = UpdateSecretResponse response -> case response of Ok ( _, secret ) -> - ( model + ( { model + | secret = RemoteData.succeed secret + , form = Components.SecretForm.toForm secret + } , Effect.addAlertSuccess { content = "Updated org secret '" ++ secret.name ++ "'." , addToastIfUnique = True diff --git a/src/elm/Pages/Dash/Secrets/Engine_/Repo/Org_/Repo_.elm b/src/elm/Pages/Dash/Secrets/Engine_/Repo/Org_/Repo_.elm index bcc0d59e4..86831b172 100644 --- a/src/elm/Pages/Dash/Secrets/Engine_/Repo/Org_/Repo_.elm +++ b/src/elm/Pages/Dash/Secrets/Engine_/Repo/Org_/Repo_.elm @@ -204,29 +204,7 @@ update shared route msg model = -- REFRESH Tick options -> - ( model - , Effect.batch - [ Effect.getRepoSecrets - { baseUrl = shared.velaAPIBaseURL - , session = shared.session - , onResponse = GetRepoSecretsResponse - , pageNumber = Dict.get "page" route.query |> Maybe.andThen String.toInt - , perPage = Dict.get "perPage" route.query |> Maybe.andThen String.toInt - , engine = route.params.engine - , org = route.params.org - , repo = route.params.repo - } - , Effect.getOrgSecrets - { baseUrl = shared.velaAPIBaseURL - , session = shared.session - , onResponse = GetOrgSecretsResponse - , pageNumber = Nothing - , perPage = Nothing - , engine = route.params.engine - , org = route.params.org - } - ] - ) + ( model, Effect.none ) diff --git a/src/elm/Pages/Dash/Secrets/Engine_/Repo/Org_/Repo_/Name_.elm b/src/elm/Pages/Dash/Secrets/Engine_/Repo/Org_/Repo_/Name_.elm index f2569686f..494d15b6e 100644 --- a/src/elm/Pages/Dash/Secrets/Engine_/Repo/Org_/Repo_/Name_.elm +++ b/src/elm/Pages/Dash/Secrets/Engine_/Repo/Org_/Repo_/Name_.elm @@ -171,7 +171,10 @@ update shared route msg model = UpdateSecretResponse response -> case response of Ok ( _, secret ) -> - ( model + ( { model + | secret = RemoteData.succeed secret + , form = Components.SecretForm.toForm secret + } , Effect.addAlertSuccess { content = "Updated repo secret '" ++ route.params.name ++ "'." , addToastIfUnique = True diff --git a/src/elm/Pages/Dash/Secrets/Engine_/Shared/Org_/Team_.elm b/src/elm/Pages/Dash/Secrets/Engine_/Shared/Org_/Team_.elm index b1b3cae1a..b20ad5c95 100644 --- a/src/elm/Pages/Dash/Secrets/Engine_/Shared/Org_/Team_.elm +++ b/src/elm/Pages/Dash/Secrets/Engine_/Shared/Org_/Team_.elm @@ -176,18 +176,7 @@ update shared route msg model = -- REFRESH Tick options -> - ( model - , Effect.getSharedSecrets - { baseUrl = shared.velaAPIBaseURL - , session = shared.session - , onResponse = GetSharedSecretsResponse - , pageNumber = Dict.get "page" route.query |> Maybe.andThen String.toInt - , perPage = Dict.get "perPage" route.query |> Maybe.andThen String.toInt - , engine = route.params.engine - , org = route.params.org - , team = route.params.team - } - ) + ( model, Effect.none ) diff --git a/src/elm/Pages/Dash/Secrets/Engine_/Shared/Org_/Team_/Name_.elm b/src/elm/Pages/Dash/Secrets/Engine_/Shared/Org_/Team_/Name_.elm index 99b4982ab..143636f9c 100644 --- a/src/elm/Pages/Dash/Secrets/Engine_/Shared/Org_/Team_/Name_.elm +++ b/src/elm/Pages/Dash/Secrets/Engine_/Shared/Org_/Team_/Name_.elm @@ -166,7 +166,10 @@ update shared route msg model = UpdateSecretResponse response -> case response of Ok ( _, secret ) -> - ( model + ( { model + | secret = RemoteData.succeed secret + , form = Components.SecretForm.toForm secret + } , Effect.addAlertSuccess { content = "Updated shared secret '" ++ route.params.name ++ "'." , addToastIfUnique = True diff --git a/src/elm/Pages/Dashboards.elm b/src/elm/Pages/Dashboards.elm index 09c63b37f..66617ad06 100644 --- a/src/elm/Pages/Dashboards.elm +++ b/src/elm/Pages/Dashboards.elm @@ -7,16 +7,25 @@ module Pages.Dashboards exposing (Model, Msg, page) import Auth import Components.Crumbs +import Components.Loading import Components.Nav +import Components.Svgs import Effect exposing (Effect) -import Html exposing (code, h1, h2, main_, p, text) +import Html exposing (Html, a, code, div, h1, h2, li, main_, p, span, text, ul) import Html.Attributes exposing (class) +import Http +import Http.Detailed import Layouts import Page exposing (Page) +import RemoteData exposing (WebData) import Route exposing (Route) import Route.Path import Shared +import Time +import Utils.Errors as Errors import Utils.Helpers as Util +import Utils.Interval as Interval +import Vela import View exposing (View) @@ -74,15 +83,19 @@ toLayout user route model = {-| Model : alias for a model object for the dashboards page. -} type alias Model = - {} + { dashboards : WebData (List Vela.Dashboard) } {-| init : takes shared model and initializes dashboards page input arguments. -} init : Shared.Model -> Route () -> () -> ( Model, Effect Msg ) init shared route () = - ( {} - , Effect.none + ( { dashboards = RemoteData.Loading } + , Effect.getDashboards + { baseUrl = shared.velaAPIBaseURL + , session = shared.session + , onResponse = GetDashboardsResponse + } ) @@ -93,7 +106,9 @@ init shared route () = {-| Msg : custom type with possible messages. -} type Msg - = NoOp + = GetDashboardsResponse (Result (Http.Detailed.Error String) ( Http.Metadata, List Vela.Dashboard )) + -- REFRESH + | Tick { time : Time.Posix, interval : Interval.Interval } {-| update : takes current model, message, and returns an updated model and effect. @@ -101,9 +116,29 @@ type Msg update : Shared.Model -> Route () -> Msg -> Model -> ( Model, Effect Msg ) update shared route msg model = case msg of - NoOp -> + GetDashboardsResponse response -> + case response of + Ok ( _, dashboards ) -> + ( { model | dashboards = RemoteData.Success dashboards } + , Effect.none + ) + + Err error -> + ( { model | dashboards = Errors.toFailure error } + , Effect.handleHttpError + { error = error + , shouldShowAlertFn = Errors.showAlertAlways + } + ) + + -- REFRESH + Tick options -> ( model - , Effect.none + , Effect.getDashboards + { baseUrl = shared.velaAPIBaseURL + , session = shared.session + , onResponse = GetDashboardsResponse + } ) @@ -115,7 +150,7 @@ update shared route msg model = -} subscriptions : Model -> Sub Msg subscriptions model = - Sub.none + Interval.tickEveryFiveSeconds Tick @@ -140,17 +175,117 @@ view shared route model = { buttons = [] , crumbs = Components.Crumbs.view route.path crumbs } - , main_ [ class "content-wrap", Util.testAttribute "dashboards" ] - [ h1 [] [ text "Welcome to dashboards!" ] - , h2 [] [ text "โœจ Want to create a new dashboard?" ] - , p [] [ text "Use the Vela CLI to add a new dashboard:" ] - , code [ class "shell" ] [ text "vela add dashboard --help" ] - , h2 [] [ text "๐Ÿš€ Already have a dashboard?" ] - , p [] [ text "Check your available dashboards with:" ] - , code [ class "shell" ] [ text "vela get dashboards" ] - , p [] [ text "Take note of your dashboard ID you are interested in and and add it to the current URL to view it." ] - , h2 [] [ text "๐Ÿ’ฌ Got Feedback?" ] - , p [] [ text "Follow the link in the top right to let us know your thoughts and ideas." ] + , main_ [ class "content-wrap" ] + [ div [ Util.testAttribute "dashboards" ] <| + case model.dashboards of + RemoteData.Success dashboards -> + if List.length dashboards > 0 then + [ div [ class "dashboards" ] + (h1 [] [ text "Dashboards", span [ class "beta" ] [ text "beta" ] ] + :: viewDashboards dashboards + ++ [ h2 [] [ text "๐Ÿงช Beta Limitations" ] + , p [] [ text "This is an early version of Dashboards. Please be aware of the following:" ] + , ul [] + [ li [] [ text "You have to use CLI/API to manage (add, edit, etc) dashboards" ] + , li [] [ text "This page will only show dashboards you created" ] + , li [] [ text "Bookmark or save links to dashboards you didn't create" ] + ] + , h2 [] [ text "๐Ÿ’ฌ Got Feedback?" ] + , p [] [ text "Help us shape Dashboards. What do you want to see? Use the \"feedback\" link in the top right!" ] + ] + ) + ] + + else + [ div [ class "dashboards" ] + [ h1 [] [ text "Welcome to Dashboards", span [ class "beta" ] [ text "beta" ] ] + , h2 [] [ text "โœจ Want to create a new dashboard?" ] + , p [] [ text "Use the Vela CLI to add a new dashboard:" ] + , code [ class "shell" ] [ text "vela add dashboard --help" ] + , p [] [ text "Once you added dashboards, they will show on this page." ] + , h2 [] [ text "๐Ÿ’ฌ Got Feedback?" ] + , p [] [ text "Follow the \"feedback\" link in the top right to let us know your thoughts and ideas." ] + ] + ] + + RemoteData.Failure error -> + [ span [] + [ text <| + case error of + Http.BadStatus statusCode -> + case statusCode of + 401 -> + "Unauthorized to retrieve dashboards" + + _ -> + "No dashboards found, there was an error with the server" + + _ -> + "No dashboards found, there was an error with the server" + ] + ] + + _ -> + [ Components.Loading.viewSmallLoader ] ] ] } + + +{-| viewDashboards : renders a list of dashboard links. +-} +viewDashboards : List Vela.Dashboard -> List (Html Msg) +viewDashboards dashboards = + dashboards + |> List.map + (\dashboard -> + let + dashboardLink = + Route.Path.Dashboards_Dashboard_ { dashboard = dashboard.dashboard.id } + |> Route.Path.href + in + div [ class "item", Util.testAttribute "dashboard-item" ] + [ span [ class "dashboard-item-title" ] + [ a [ dashboardLink ] [ text dashboard.dashboard.name ] + , code [] [ text dashboard.dashboard.id ] + ] + , div [ class "buttons" ] + [ a [ class "button", dashboardLink ] [ text "View" ] + ] + , viewDashboardRepos dashboard.repos dashboard.dashboard.id + ] + ) + + +{-| viewDashboardRepos : renders a list of repos belonging to a dashboard. +-} +viewDashboardRepos : List Vela.DashboardRepoCard -> String -> Html Msg +viewDashboardRepos repos dashboardId = + div [ class "dashboard-repos", Util.testAttribute "dashboard-repos" ] + (if List.length repos > 0 then + repos + |> List.map + (\repo -> + let + statusIcon = + case List.head repo.builds of + Just build -> + Components.Svgs.recentBuildStatusToIcon build.status 0 + + Nothing -> + Components.Svgs.recentBuildStatusToIcon Vela.Pending 0 + in + div + [ class "dashboard-repos-item" ] + [ statusIcon + , text (repo.org ++ "/" ++ repo.name) + ] + ) + + else + [ text <| + "โš ๏ธ No repositories in this dashboard. Use the CLI to add some: vela update dashboard --id " + ++ dashboardId + ++ " --add-repos org/repo" + ] + ) diff --git a/src/elm/Pages/Dashboards/Dashboard_.elm b/src/elm/Pages/Dashboards/Dashboard_.elm index 4d32587e3..30335ba02 100644 --- a/src/elm/Pages/Dashboards/Dashboard_.elm +++ b/src/elm/Pages/Dashboards/Dashboard_.elm @@ -36,7 +36,6 @@ import Route.Path import Shared import Time import Utils.Errors as Errors -import Utils.Favicons as Favicons import Utils.Helpers as Util import Utils.Interval as Interval import Vela @@ -202,7 +201,7 @@ view shared route model = crumbs = [ ( "Overview", Just Route.Path.Home_ ) - , ( "Dashboards", Nothing ) + , ( "Dashboards", Just Route.Path.Dashboards ) , ( dashboardName, Nothing ) ] in diff --git a/src/elm/Pages/Home_.elm b/src/elm/Pages/Home_.elm index 1dd7646e2..72754f9c9 100644 --- a/src/elm/Pages/Home_.elm +++ b/src/elm/Pages/Home_.elm @@ -138,7 +138,7 @@ update msg model = -- REFRESH Tick options -> ( model - , Effect.getCurrentUserShared {} + , Effect.none ) @@ -172,6 +172,13 @@ view shared route model = route { buttons = [ a + [ class "button" + , class "-outline" + , Util.testAttribute "dashboards-button" + , Route.Path.href Route.Path.Dashboards + ] + [ text "Dashboards" ] + , a [ class "button" , class "-outline" , Util.testAttribute "source-repos" diff --git a/src/elm/Pages/Org_.elm b/src/elm/Pages/Org_.elm index ca2ef1682..7696caf5c 100644 --- a/src/elm/Pages/Org_.elm +++ b/src/elm/Pages/Org_.elm @@ -168,16 +168,7 @@ update shared route msg model = -- REFRESH Tick options -> - ( model - , Effect.getOrgRepos - { baseUrl = shared.velaAPIBaseURL - , session = shared.session - , onResponse = GetOrgReposResponse - , org = route.params.org - , pageNumber = Dict.get "page" route.query |> Maybe.andThen String.toInt - , perPage = Dict.get "perPage" route.query |> Maybe.andThen String.toInt - } - ) + ( model, Effect.none ) diff --git a/src/elm/Pages/Org_/Repo_/Build_.elm b/src/elm/Pages/Org_/Repo_/Build_.elm index bd9d63a41..6546e87c2 100644 --- a/src/elm/Pages/Org_/Repo_/Build_.elm +++ b/src/elm/Pages/Org_/Repo_/Build_.elm @@ -195,7 +195,7 @@ init shared route () = , logFollow = 0 } , Effect.batch - [ Effect.getBuildSteps + [ Effect.getAllBuildSteps { baseUrl = shared.velaAPIBaseURL , session = shared.session , onResponse = @@ -206,8 +206,6 @@ init shared route () = |> Maybe.withDefault "false" |> (==) "false" } - , pageNumber = Nothing - , perPage = Just 100 , org = route.params.org , repo = route.params.repo , build = route.params.build @@ -235,7 +233,7 @@ type Msg | GetBuildStepLogResponse { step : Vela.Step, applyDomFocus : Bool, previousFocus : Maybe Focus.Focus } (Result (Http.Detailed.Error String) ( Http.Metadata, Vela.Log )) | GetBuildStepLogRefreshResponse { step : Vela.Step } (Result (Http.Detailed.Error String) ( Http.Metadata, Vela.Log )) | ClickStep { step : Vela.Step } - | ExpandStep { step : Vela.Step, applyDomFocus : Bool, previousFocus : Maybe Focus.Focus } + | ExpandStep { step : Vela.Step, applyDomFocus : Bool, previousFocus : Maybe Focus.Focus, triggeredFromClick : Bool } | CollapseStep { step : Vela.Step } | ExpandAll | CollapseAll @@ -265,7 +263,15 @@ update shared route msg model = } , RemoteData.withDefault [] model.steps |> List.filter (\s -> Maybe.withDefault -1 focus.group == s.number) - |> List.map (\s -> ExpandStep { step = s, applyDomFocus = True, previousFocus = Just model.focus }) + |> List.map + (\s -> + ExpandStep + { step = s + , applyDomFocus = True + , previousFocus = Just model.focus + , triggeredFromClick = False + } + ) |> List.map Effect.sendMsg |> Effect.batch ) @@ -302,6 +308,7 @@ update shared route msg model = { step = step , applyDomFocus = options.applyDomFocus , previousFocus = Nothing + , triggeredFromClick = False } |> Effect.sendMsg ) @@ -322,6 +329,13 @@ update shared route msg model = ( { model | steps = RemoteData.succeed <| List.sortBy .number steps } , steps |> List.filter (\step -> List.member step.number model.viewing) + -- note: it's possible that there are log updates in flight + -- even after the step has a status of finished, especially + -- for large logs. we get the most recent version of logs + -- on page load or when a step log is expanded, so potentially + -- seeing incomplete logs here is only a concern when someone + -- is following the logs live. + |> List.filter (\step -> step.finished == 0) |> List.map (\step -> Effect.getBuildStepLog @@ -432,7 +446,12 @@ update shared route msg model = else Effect.batch - [ ExpandStep { step = options.step, applyDomFocus = False, previousFocus = Nothing } + [ ExpandStep + { step = options.step + , applyDomFocus = False + , previousFocus = Nothing + , triggeredFromClick = True + } |> Effect.sendMsg , case model.focus.a of Nothing -> @@ -452,25 +471,57 @@ update shared route msg model = ) ExpandStep options -> - ( { model - | viewing = List.Extra.unique <| options.step.number :: model.viewing - } - , Effect.batch - [ Effect.getBuildStepLog - { baseUrl = shared.velaAPIBaseURL - , session = shared.session - , onResponse = - GetBuildStepLogResponse - { step = options.step - , applyDomFocus = options.applyDomFocus - , previousFocus = options.previousFocus - } - , org = route.params.org - , repo = route.params.repo - , build = route.params.build - , stepNumber = String.fromInt options.step.number - } - , if options.applyDomFocus then + let + isFromHashChanged = + options.previousFocus /= Nothing + + didFocusChange = + case options.previousFocus of + Just f -> + f.group /= model.focus.group + + Nothing -> + False + + -- hash will change when no line is selected and the selected group changes + -- this means expansion msg will double up on fetching logs unless instructed not to + willFocusChange = + case ( model.focus.group, model.focus.a, model.focus.b ) of + ( Just g, Nothing, _ ) -> + g /= options.step.number + + _ -> + False + + isLogLoaded = + Dict.get options.step.id model.logs + |> Maybe.withDefault RemoteData.Loading + |> Util.isLoaded + + -- fetch logs when expansion msg meets the criteria: + -- triggered by a click that will change the hash + -- the focus changes and the logs are not loaded + fetchLogs = + not (options.triggeredFromClick && willFocusChange) + && ((didFocusChange && not isLogLoaded) || not isFromHashChanged) + + getLogEffect = + Effect.getBuildStepLog + { baseUrl = shared.velaAPIBaseURL + , session = shared.session + , onResponse = + GetBuildStepLogResponse + { step = options.step + , applyDomFocus = options.applyDomFocus + , previousFocus = options.previousFocus + } + , org = route.params.org + , repo = route.params.repo + , build = route.params.build + , stepNumber = String.fromInt options.step.number + } + + applyDomFocusEffect = case ( model.focus.group, model.focus.a, model.focus.b ) of ( Just g, Nothing, Nothing ) -> FocusOn @@ -486,9 +537,23 @@ update shared route msg model = _ -> Effect.none - else - Effect.none - ] + runEffects = + [ if fetchLogs then + getLogEffect + + else + Effect.none + , if options.applyDomFocus then + applyDomFocusEffect + + else + Effect.none + ] + in + ( { model + | viewing = List.Extra.unique <| options.step.number :: model.viewing + } + , Effect.batch runEffects ) CollapseStep options -> @@ -518,6 +583,7 @@ update shared route msg model = { step = step , applyDomFocus = False , previousFocus = Nothing + , triggeredFromClick = False } ) |> List.map Effect.sendMsg @@ -546,17 +612,31 @@ update shared route msg model = -- REFRESH Tick options -> + let + isAnyStepRunning = + case model.steps of + RemoteData.Success steps -> + List.any (\s -> s.finished == 0) steps + + _ -> + False + + runEffect = + if isAnyStepRunning then + Effect.getAllBuildSteps + { baseUrl = shared.velaAPIBaseURL + , session = shared.session + , onResponse = GetBuildStepsRefreshResponse + , org = route.params.org + , repo = route.params.repo + , build = route.params.build + } + + else + Effect.none + in ( model - , Effect.getBuildSteps - { baseUrl = shared.velaAPIBaseURL - , session = shared.session - , onResponse = GetBuildStepsRefreshResponse - , pageNumber = Nothing - , perPage = Just 100 - , org = route.params.org - , repo = route.params.repo - , build = route.params.build - } + , runEffect ) diff --git a/src/elm/Pages/Org_/Repo_/Build_/Graph.elm b/src/elm/Pages/Org_/Repo_/Build_/Graph.elm index 083f28cbd..b62ba9623 100644 --- a/src/elm/Pages/Org_/Repo_/Build_/Graph.elm +++ b/src/elm/Pages/Org_/Repo_/Build_/Graph.elm @@ -20,6 +20,7 @@ import Http import Http.Detailed import Interop import Layouts +import List.Extra import Page exposing (Page) import Pages.Account.Login exposing (Msg(..)) import RemoteData exposing (RemoteData(..), WebData) @@ -237,6 +238,31 @@ update shared route msg model = ) Refresh options -> + let + isBuildRunning = + case shared.builds of + RemoteData.Success builds -> + List.Extra.find (\b -> b.number == Maybe.withDefault 0 (String.toInt route.params.build)) builds + |> Maybe.map (\b -> b.finished == 0) + |> Maybe.withDefault False + + _ -> + False + + runEffect = + if isBuildRunning || options.freshDraw then + Effect.getBuildGraph + { baseUrl = shared.velaAPIBaseURL + , session = shared.session + , onResponse = GetBuildGraphResponse { freshDraw = options.freshDraw } + , org = route.params.org + , repo = route.params.repo + , build = route.params.build + } + + else + Effect.none + in ( { model | graph = if options.setToLoading then @@ -246,14 +272,7 @@ update shared route msg model = model.graph } , Effect.batch - [ Effect.getBuildGraph - { baseUrl = shared.velaAPIBaseURL - , session = shared.session - , onResponse = GetBuildGraphResponse { freshDraw = options.freshDraw } - , org = route.params.org - , repo = route.params.repo - , build = route.params.build - } + [ runEffect , if options.clear then Effect.sendCmd clearBuildGraph @@ -337,9 +356,16 @@ update shared route msg model = -- REFRESH Tick options -> - ( model - , Effect.sendMsg <| Refresh { freshDraw = False, setToLoading = False, clear = False } - ) + case options.interval of + Interval.FiveSeconds -> + ( model + , Effect.sendMsg <| Refresh { freshDraw = False, setToLoading = False, clear = False } + ) + + Interval.OneSecond -> + ( model + , Effect.sendCmd <| renderBuildGraph shared model { freshDraw = False } + ) @@ -357,6 +383,7 @@ subscriptions model = -- on visiblity changed, same as shared , Browser.Events.onVisibilityChange (\visibility -> VisibilityChanged { visibility = visibility }) + , Interval.tickEveryFiveSeconds Tick , Interval.tickEveryOneSecond Tick ] diff --git a/src/elm/Pages/Org_/Repo_/Build_/Services.elm b/src/elm/Pages/Org_/Repo_/Build_/Services.elm index a483bb64b..2933502d0 100644 --- a/src/elm/Pages/Org_/Repo_/Build_/Services.elm +++ b/src/elm/Pages/Org_/Repo_/Build_/Services.elm @@ -195,12 +195,10 @@ init shared route () = , logFollow = 0 } , Effect.batch - [ Effect.getBuildServices + [ Effect.getAllBuildServices { baseUrl = shared.velaAPIBaseURL , session = shared.session , onResponse = GetBuildServicesResponse - , pageNumber = Nothing - , perPage = Just 100 , org = route.params.org , repo = route.params.repo , build = route.params.build @@ -227,7 +225,7 @@ type Msg | GetBuildServiceLogResponse { service : Vela.Service, applyDomFocus : Bool, previousFocus : Maybe Focus.Focus } (Result (Http.Detailed.Error String) ( Http.Metadata, Vela.Log )) | GetBuildServiceLogRefreshResponse { service : Vela.Service } (Result (Http.Detailed.Error String) ( Http.Metadata, Vela.Log )) | ClickService { service : Vela.Service } - | ExpandService { service : Vela.Service, applyDomFocus : Bool, previousFocus : Maybe Focus.Focus } + | ExpandService { service : Vela.Service, applyDomFocus : Bool, previousFocus : Maybe Focus.Focus, triggeredFromClick : Bool } | CollapseService { service : Vela.Service } | ExpandAll | CollapseAll @@ -257,7 +255,15 @@ update shared route msg model = } , RemoteData.withDefault [] model.services |> List.filter (\s -> Maybe.withDefault -1 focus.group == s.number) - |> List.map (\s -> ExpandService { service = s, applyDomFocus = True, previousFocus = Just model.focus }) + |> List.map + (\s -> + ExpandService + { service = s + , applyDomFocus = True + , previousFocus = Just model.focus + , triggeredFromClick = False + } + ) |> List.map Effect.sendMsg |> Effect.batch ) @@ -293,6 +299,7 @@ update shared route msg model = { service = service , applyDomFocus = True , previousFocus = Nothing + , triggeredFromClick = False } |> Effect.sendMsg ) @@ -313,6 +320,13 @@ update shared route msg model = ( { model | services = RemoteData.succeed services } , services |> List.filter (\service -> List.member service.number model.viewing) + -- note: it's possible that there are log updates in flight + -- even after the service has a status of finished, especially + -- for large logs. we get the most recent version of logs + -- on page load or when a service log is expanded, so potentially + -- seeing incomplete logs here is only a concern when someone + -- is following the logs live. + |> List.filter (\service -> service.finished == 0) |> List.map (\service -> Effect.getBuildServiceLog @@ -423,7 +437,12 @@ update shared route msg model = else Effect.batch - [ ExpandService { service = options.service, applyDomFocus = False, previousFocus = Nothing } + [ ExpandService + { service = options.service + , applyDomFocus = False + , previousFocus = Nothing + , triggeredFromClick = True + } |> Effect.sendMsg , case model.focus.a of Nothing -> @@ -443,25 +462,57 @@ update shared route msg model = ) ExpandService options -> - ( { model - | viewing = List.Extra.unique <| options.service.number :: model.viewing - } - , Effect.batch - [ Effect.getBuildServiceLog - { baseUrl = shared.velaAPIBaseURL - , session = shared.session - , onResponse = - GetBuildServiceLogResponse - { service = options.service - , applyDomFocus = options.applyDomFocus - , previousFocus = options.previousFocus - } - , org = route.params.org - , repo = route.params.repo - , build = route.params.build - , serviceNumber = String.fromInt options.service.number - } - , if options.applyDomFocus then + let + isFromHashChanged = + options.previousFocus /= Nothing + + didFocusChange = + case options.previousFocus of + Just f -> + f.group /= model.focus.group + + Nothing -> + False + + -- hash will change when no line is selected and the selected group changes + -- this means the expansion msg will double up on fetching logs unless instructed not to + willFocusChange = + case ( model.focus.group, model.focus.a, model.focus.b ) of + ( Just g, Nothing, _ ) -> + g /= options.service.number + + _ -> + False + + isLogLoaded = + Dict.get options.service.id model.logs + |> Maybe.withDefault RemoteData.Loading + |> Util.isLoaded + + -- fetch logs when expansion msg meets the criteria: + -- triggered by a click that will change the hash + -- the focus changes and the logs are not loaded + fetchLogs = + not (options.triggeredFromClick && willFocusChange) + && ((didFocusChange && not isLogLoaded) || not isFromHashChanged) + + getLogEffect = + Effect.getBuildServiceLog + { baseUrl = shared.velaAPIBaseURL + , session = shared.session + , onResponse = + GetBuildServiceLogResponse + { service = options.service + , applyDomFocus = options.applyDomFocus + , previousFocus = options.previousFocus + } + , org = route.params.org + , repo = route.params.repo + , build = route.params.build + , serviceNumber = String.fromInt options.service.number + } + + applyDomFocusEffect = case ( model.focus.group, model.focus.a, model.focus.b ) of ( Just g, Nothing, Nothing ) -> FocusOn @@ -477,9 +528,24 @@ update shared route msg model = _ -> Effect.none - else - Effect.none - ] + runEffects = + [ if fetchLogs then + getLogEffect + + else + Effect.none + , if options.applyDomFocus then + applyDomFocusEffect + + else + Effect.none + ] + in + ( { model + | viewing = List.Extra.unique <| options.service.number :: model.viewing + } + , Effect.batch + runEffects ) CollapseService options -> @@ -509,6 +575,7 @@ update shared route msg model = { service = service , applyDomFocus = False , previousFocus = Nothing + , triggeredFromClick = False } ) |> List.map Effect.sendMsg @@ -537,17 +604,31 @@ update shared route msg model = -- REFRESH Tick options -> + let + isAnyServiceRunning = + case model.services of + RemoteData.Success services -> + List.any (\s -> s.status == Vela.Running) services + + _ -> + False + + runEffect = + if isAnyServiceRunning then + Effect.getAllBuildServices + { baseUrl = shared.velaAPIBaseURL + , session = shared.session + , onResponse = GetBuildServicesRefreshResponse + , org = route.params.org + , repo = route.params.repo + , build = route.params.build + } + + else + Effect.none + in ( model - , Effect.getBuildServices - { baseUrl = shared.velaAPIBaseURL - , session = shared.session - , onResponse = GetBuildServicesRefreshResponse - , pageNumber = Nothing - , perPage = Just 100 - , org = route.params.org - , repo = route.params.repo - , build = route.params.build - } + , runEffect ) diff --git a/src/elm/Pages/Org_/Repo_/Deployments.elm b/src/elm/Pages/Org_/Repo_/Deployments.elm index 23530f7d8..c6a168872 100644 --- a/src/elm/Pages/Org_/Repo_/Deployments.elm +++ b/src/elm/Pages/Org_/Repo_/Deployments.elm @@ -20,6 +20,7 @@ import Html , a , div , span + , td , text , tr ) @@ -29,6 +30,7 @@ import Html.Attributes , class , href , rows + , scope ) import Http import Http.Detailed @@ -369,6 +371,7 @@ tableHeaders = , ( Nothing, "commit" ) , ( Nothing, "ref" ) , ( Nothing, "description" ) + , ( Nothing, "parameters" ) , ( Nothing, "builds" ) , ( Nothing, "created by" ) , ( Nothing, "created at" ) @@ -432,6 +435,21 @@ viewDeployment shared repo deployment = [ text deployment.description ] } + , td + [ attribute "data-label" "parameters" + , scope "row" + , class "break-word" + ] + [ Components.Table.viewListCell + { dataLabel = "parameters" + , items = + deployment.payload + |> Maybe.withDefault [] + |> List.map (\parameter -> parameter.key ++ "=" ++ parameter.value) + , none = "no parameters" + , itemWrapperClassList = [] + } + ] , Components.Table.viewItemCell { dataLabel = "builds" , parentClassList = [] diff --git a/src/elm/Pages/Org_/Repo_/Hooks.elm b/src/elm/Pages/Org_/Repo_/Hooks.elm index 5252f2805..cf95e6a08 100644 --- a/src/elm/Pages/Org_/Repo_/Hooks.elm +++ b/src/elm/Pages/Org_/Repo_/Hooks.elm @@ -157,91 +157,96 @@ type Msg -} update : Shared.Model -> Route { org : String, repo : String } -> Msg -> Model -> ( Model, Effect Msg ) update shared route msg model = - case msg of - -- HOOKS - GetRepoHooksResponse response -> - case response of - Ok ( meta, hooks ) -> - ( { model - | hooks = RemoteData.succeed hooks - , pager = Api.Pagination.get meta.headers - } - , Effect.none - ) - - Err error -> - ( { model | hooks = Errors.toFailure error } - , Effect.handleHttpError - { error = error - , shouldShowAlertFn = Errors.showAlertAlways - } - ) - - RedeliverRepoHook options -> - ( model - , Effect.redeliverHook - { baseUrl = shared.velaAPIBaseURL - , session = shared.session - , onResponse = RedeliverRepoHookResponse options - , org = route.params.org - , repo = route.params.repo - , hookNumber = String.fromInt <| options.hook.number - } - ) - - RedeliverRepoHookResponse options response -> - case response of - Ok ( _, result ) -> - ( model - , Effect.addAlertSuccess - { content = "Hook #" ++ (String.fromInt <| options.hook.number) ++ " redelivered successfully." - , addToastIfUnique = False - , link = Nothing + -- persist any hooks updates to the shared model + (\( m, e ) -> + ( m, Effect.batch [ e, Effect.updateRepoHooksShared { hooks = m.hooks } ] ) + ) + <| + case msg of + -- HOOKS + GetRepoHooksResponse response -> + case response of + Ok ( meta, hooks ) -> + ( { model + | hooks = RemoteData.succeed hooks + , pager = Api.Pagination.get meta.headers + } + , Effect.none + ) + + Err error -> + ( { model | hooks = Errors.toFailure error } + , Effect.handleHttpError + { error = error + , shouldShowAlertFn = Errors.showAlertAlways + } + ) + + RedeliverRepoHook options -> + ( model + , Effect.redeliverHook + { baseUrl = shared.velaAPIBaseURL + , session = shared.session + , onResponse = RedeliverRepoHookResponse options + , org = route.params.org + , repo = route.params.repo + , hookNumber = String.fromInt <| options.hook.number + } + ) + + RedeliverRepoHookResponse options response -> + case response of + Ok ( _, result ) -> + ( model + , Effect.addAlertSuccess + { content = "Hook #" ++ (String.fromInt <| options.hook.number) ++ " redelivered successfully." + , addToastIfUnique = False + , link = Nothing + } + ) + + Err error -> + ( model + , Effect.handleHttpError + { error = error + , shouldShowAlertFn = Errors.showAlertAlways + } + ) + + GotoPage pageNumber -> + ( model + , Effect.batch + [ Effect.replaceRoute + { path = route.path + , query = + Dict.update "page" (\_ -> Just <| String.fromInt pageNumber) route.query + , hash = route.hash } - ) - - Err error -> - ( model - , Effect.handleHttpError - { error = error - , shouldShowAlertFn = Errors.showAlertAlways + , Effect.getRepoHooks + { baseUrl = shared.velaAPIBaseURL + , session = shared.session + , onResponse = GetRepoHooksResponse + , pageNumber = Just pageNumber + , perPage = Dict.get "perPage" route.query |> Maybe.andThen String.toInt + , org = route.params.org + , repo = route.params.repo } - ) + ] + ) - GotoPage pageNumber -> - ( model - , Effect.batch - [ Effect.replaceRoute - { path = route.path - , query = - Dict.update "page" (\_ -> Just <| String.fromInt pageNumber) route.query - , hash = route.hash - } + -- REFRESH + Tick options -> + ( model , Effect.getRepoHooks { baseUrl = shared.velaAPIBaseURL , session = shared.session , onResponse = GetRepoHooksResponse - , pageNumber = Just pageNumber + , pageNumber = Dict.get "page" route.query |> Maybe.andThen String.toInt , perPage = Dict.get "perPage" route.query |> Maybe.andThen String.toInt , org = route.params.org , repo = route.params.repo } - ] - ) - - -- REFRESH - Tick options -> - ( model - , Effect.getRepoHooks - { baseUrl = shared.velaAPIBaseURL - , session = shared.session - , onResponse = GetRepoHooksResponse - , pageNumber = Dict.get "page" route.query |> Maybe.andThen String.toInt - , perPage = Dict.get "perPage" route.query |> Maybe.andThen String.toInt - , org = route.params.org - , repo = route.params.repo - } - ) + ) diff --git a/src/elm/Shared.elm b/src/elm/Shared.elm index ef12ac393..b90d960e3 100644 --- a/src/elm/Shared.elm +++ b/src/elm/Shared.elm @@ -57,6 +57,7 @@ type alias Flags = , velaRedirect : String , velaLogBytesLimit : Int , velaMaxBuildLimit : Int + , velaMaxStarlarkExecLimit : Int , velaScheduleAllowlist : String } @@ -72,6 +73,7 @@ decoder = |> required "velaRedirect" Json.Decode.string |> required "velaLogBytesLimit" Json.Decode.int |> required "velaMaxBuildLimit" Json.Decode.int + |> required "velaMaxStarlarkExecLimit" Json.Decode.int |> required "velaScheduleAllowlist" Json.Decode.string @@ -95,6 +97,7 @@ init flagsResult route = , velaRedirect = "" , velaLogBytesLimit = 0 , velaMaxBuildLimit = 0 + , velaMaxStarlarkExecLimit = 0 , velaScheduleAllowlist = "" } @@ -134,6 +137,7 @@ init flagsResult route = , velaRedirect = flags.velaRedirect , velaLogBytesLimit = flags.velaLogBytesLimit , velaMaxBuildLimit = flags.velaMaxBuildLimit + , velaMaxStarlarkExecLimit = flags.velaMaxStarlarkExecLimit , velaScheduleAllowlist = Util.stringToAllowlist flags.velaScheduleAllowlist -- BASE URL @@ -612,6 +616,11 @@ update route msg model = } ) + Shared.Msg.UpdateRepoHooks options -> + ( { model | hooks = options.hooks } + , Effect.none + ) + -- THEME Shared.Msg.SetTheme options -> if options.theme == model.theme then @@ -662,21 +671,24 @@ update route msg model = let ( shared, redirect ) = case options.error of - -- todo: maybe we pass in a status code we want to ignore - -- so secrets can skip this alert for 401s - -- - -- Http.Detailed.BadStatus meta _ -> - -- case meta.statusCode of - -- -- todo: FIX THIS! secrets can easily return a 401 for normal reasons - -- 401 -> - -- ( { model - -- | session = Auth.Session.Unauthenticated - -- , velaRedirect = "/" - -- } - -- , Effect.replacePath <| Route.Path.Account_Login - -- ) - -- _ -> - -- ( model, Effect.none ) + -- only handles token expiration on 401s + -- TODO: handle 401s for access restriction reasons + Http.Detailed.BadStatus meta body -> + let + isTokenExpired = + String.contains "token is expired" body + in + if meta.statusCode == 401 && isTokenExpired then + ( { model + | session = Auth.Session.Unauthenticated + , velaRedirect = Route.Path.toString route.path + } + , Effect.replacePath <| Route.Path.Account_Login + ) + + else + ( model, Effect.none ) + _ -> ( model, Effect.none ) in diff --git a/src/elm/Shared/Model.elm b/src/elm/Shared/Model.elm index 4c1a2c031..559027eb7 100644 --- a/src/elm/Shared/Model.elm +++ b/src/elm/Shared/Model.elm @@ -26,6 +26,7 @@ type alias Model = , velaRedirect : String , velaLogBytesLimit : Int , velaMaxBuildLimit : Int + , velaMaxStarlarkExecLimit : Int , velaScheduleAllowlist : List ( String, String ) -- BASE URL diff --git a/src/elm/Shared/Msg.elm b/src/elm/Shared/Msg.elm index c8a7c29cb..1392ba7ce 100644 --- a/src/elm/Shared/Msg.elm +++ b/src/elm/Shared/Msg.elm @@ -59,6 +59,7 @@ type Msg -- HOOKS | GetRepoHooks { org : String, repo : String, pageNumber : Maybe Int, perPage : Maybe Int, maybeEvent : Maybe String } | GetRepoHooksResponse (Result (Http.Detailed.Error String) ( Http.Metadata, List Vela.Hook )) + | UpdateRepoHooks { hooks : WebData (List Vela.Hook) } -- THEME | SetTheme { theme : Theme.Theme } -- ALERTS diff --git a/src/elm/Utils/Helpers.elm b/src/elm/Utils/Helpers.elm index 470bf98ff..6b3b8f532 100644 --- a/src/elm/Utils/Helpers.elm +++ b/src/elm/Utils/Helpers.elm @@ -26,7 +26,7 @@ module Utils.Helpers exposing , humanReadableDateTimeFormatter , humanReadableDateTimeWithDefault , humanReadableDateWithDefault - , isLoading + , isLoaded , isSuccess , mergeListsById , noBlanks @@ -375,17 +375,20 @@ fiveSecondsMillis = oneSecondMillis * 5 -{-| isLoading : takes WebData and returns true if it is in a Loading state. +{-| isLoaded : takes WebData and returns true if it is in a 'loaded' state, meaning its anything but NotAsked or Loading. -} -isLoading : WebData a -> Bool -isLoading status = +isLoaded : WebData a -> Bool +isLoaded status = case status of RemoteData.Loading -> - True + False - _ -> + RemoteData.NotAsked -> False + _ -> + True + {-| isSuccess : takes WebData and returns true if it is in a Success state. -} diff --git a/src/elm/Vela.elm b/src/elm/Vela.elm index 08c9bf11f..8f7d61f86 100644 --- a/src/elm/Vela.elm +++ b/src/elm/Vela.elm @@ -59,6 +59,7 @@ module Vela exposing , decodeBuildGraph , decodeBuilds , decodeDashboard + , decodeDashboards , decodeDeployment , decodeDeployments , decodeGraphInteraction @@ -74,9 +75,11 @@ module Vela exposing , decodeSchedules , decodeSecret , decodeSecrets + , decodeService , decodeServices , decodeSettings , decodeSourceRepositories + , decodeStep , decodeSteps , decodeUser , decodeWorkers @@ -207,6 +210,7 @@ type alias DashboardRepoCard = { org : String , name : String , counter : Int + , active : Bool , builds : List Build } @@ -218,6 +222,11 @@ decodeDashboard = |> optional "repos" (Json.Decode.list decodeDashboardRepoCard) [] +decodeDashboards : Decoder (List Dashboard) +decodeDashboards = + Json.Decode.list decodeDashboard + + decodeDashboardItem : Decoder DashboardItem decodeDashboardItem = Json.Decode.succeed DashboardItem @@ -237,6 +246,7 @@ decodeDashboardRepoCard = |> optional "org" string "" |> optional "name" string "" |> optional "counter" int -1 + |> optional "active" bool False |> optional "builds" (Json.Decode.list decodeBuild) [] @@ -257,6 +267,7 @@ type alias User = { id : Int , name : String , favorites : List String + , dashboards : List String , active : Bool , admin : Bool } @@ -268,13 +279,14 @@ decodeUser = |> required "id" int |> required "name" string |> optional "favorites" (Json.Decode.list string) [] + |> optional "dashboards" (Json.Decode.list string) [] |> required "active" bool |> optional "admin" bool False emptyUser : User emptyUser = - { id = -1, name = "", favorites = [], active = False, admin = False } + { id = -1, name = "", favorites = [], dashboards = [], active = False, admin = False } type alias UpdateUserPayload = diff --git a/src/scss/_dashboards.scss b/src/scss/_dashboards.scss index a7eaeda9b..a5bb7250e 100644 --- a/src/scss/_dashboards.scss +++ b/src/scss/_dashboards.scss @@ -2,6 +2,88 @@ // styles for the dashboard pages +.beta { + margin-left: 0.5rem; + padding: 0.25rem 0.4rem; + + font-size: 0.8rem; + vertical-align: top; + + background-color: var(--color-green-dark); +} + +.dashboards .item { + flex-wrap: wrap; +} + +.dashboard-repos { + display: flex; + flex-basis: 100%; + flex-wrap: wrap; + gap: 1rem; + margin-top: 1rem; + padding: 1rem 1rem 0 0; + + border-top: 2px dotted var(--color-bg-light); +} + +.dashboard-item-title code { + margin-left: 1rem; + + color: var(--color-bg-light); + font-size: 1rem; +} + +.dashboard-repos-item { + display: flex; + gap: 0.5rem; + align-items: center; + padding: 0.25rem 0.5rem; + + font-size: 0.8rem; + + background-color: var(--color-bg-darkest); + + .-icon { + width: 1rem; + height: 1rem; + + fill: none; + + &.-running { + background-color: var(--color-yellow); + + stroke: var(--color-bg); + } + + &.-failure, + &.-error { + background-color: var(--color-red); + + stroke: var(--color-bg); + } + + &.-canceled { + background-color: var(--color-cyan-dark); + + stroke: var(--color-bg); + } + + &.-success { + background-color: var(--color-green); + + stroke: var(--color-bg); + } + + &.-pending { + background-color: var(--color-bg-light); + + fill: var(--color-bg); + stroke: var(--color-bg); + } + } +} + .dashboard-title { border-bottom: var(--line-width) solid var(--color-secondary); } diff --git a/src/static/index.d.ts b/src/static/index.d.ts index dc1984564..2e9feb12c 100644 --- a/src/static/index.d.ts +++ b/src/static/index.d.ts @@ -56,6 +56,8 @@ export type Flags = { readonly velaLogBytesLimit: number; /** @property velaMaxBuildLimit: number */ readonly velaMaxBuildLimit: number; + /** @property velaMaxStarlarkExecLimit: number */ + readonly velaMaxStarlarkExecLimit: number; /** @property velaScheduleAllowlist: string */ readonly velaScheduleAllowlist: string; }; diff --git a/src/static/index.ts b/src/static/index.ts index 8df18ab98..72da78635 100644 --- a/src/static/index.ts +++ b/src/static/index.ts @@ -3,7 +3,7 @@ // import types import * as ClipboardJS from 'clipboard'; import * as d3 from 'd3'; -import { Graphviz } from '@hpcc-js/wasm'; +import { Graphviz } from '@hpcc-js/wasm-graphviz'; import { Elm } from '../elm/Main.elm'; import '../scss/style.scss'; @@ -16,6 +16,7 @@ const feedbackURL: string = const docsURL: string = 'https://go-vela.github.io/docs'; const defaultLogBytesLimit: number = 2000000; // 2mb const maximumBuildLimit: number = 30; +const maximumStarlarkExecLimit: number = 99999; const scheduleAllowlist: string = '*'; // setup for auth redirect @@ -51,10 +52,17 @@ const flags: Flags = { ), velaMaxBuildLimit: Number( process.env.VELA_MAX_BUILD_LIMIT || - envOrNull('VELA_MAX_BUILD_LIMIT', 'VELA_MAX_BUILD_LIMIT') || + envOrNull('VELA_MAX_BUILD_LIMIT', '$VELA_MAX_BUILD_LIMIT') || maximumBuildLimit, ), - + velaMaxStarlarkExecLimit: Number( + process.env.VELA_MAX_STARLARK_EXEC_LIMIT || + envOrNull( + 'VELA_MAX_STARLARK_EXEC_LIMIT', + '$VELA_MAX_STARLARK_EXEC_LIMIT', + ) || + maximumStarlarkExecLimit, + ), velaScheduleAllowlist: (window.Cypress && window.Cypress.env('VELA_SCHEDULE_ALLOWLIST')) || process.env.VELA_SCHEDULE_ALLOWLIST ||