From c3710b7e349504abee1aa662d9b5e0780d17d0b8 Mon Sep 17 00:00:00 2001 From: RahulUbale Date: Sun, 22 Mar 2026 05:21:50 -0400 Subject: [PATCH 1/2] feat: track and display user who revokes a workflow --- api/package-lock.json | 74 +++++++++-------- api/prisma/schema.prisma | 3 + api/src/routes/workflows.js | 29 ++++++- api/tests/routes/workflows.test.js | 93 ++++++++++++++++++++++ ui/src/components/runs/Workflow.vue | 13 +++ ui/src/components/runs/WorkflowCompact.vue | 34 ++++++++ ui/vite.config.js | 13 ++- 7 files changed, 213 insertions(+), 46 deletions(-) create mode 100644 api/tests/routes/workflows.test.js diff --git a/api/package-lock.json b/api/package-lock.json index e850e223c..28c0db858 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -1378,10 +1378,11 @@ } }, "node_modules/@prisma/client": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz", - "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.18.0.tgz", + "integrity": "sha512-BWivkLh+af1kqC89zCJYkHsRcyWsM8/JHpsDMM76DjP3ZdEquJhXa4IeX+HkWPnwJ5FanxEJFZZDTWiDs/Kvyw==", "hasInstallScript": true, + "license": "Apache-2.0", "engines": { "node": ">=16.13" }, @@ -1395,48 +1396,53 @@ } }, "node_modules/@prisma/debug": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", - "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==", - "devOptional": true + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.18.0.tgz", + "integrity": "sha512-f+ZvpTLidSo3LMJxQPVgAxdAjzv5OpzAo/eF8qZqbwvgi2F5cTOI9XCpdRzJYA0iGfajjwjOKKrVq64vkxEfUw==", + "devOptional": true, + "license": "Apache-2.0" }, "node_modules/@prisma/engines": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", - "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.18.0.tgz", + "integrity": "sha512-ofmpGLeJ2q2P0wa/XaEgTnX/IsLnvSp/gZts0zjgLNdBhfuj2lowOOPmDcfKljLQUXMvAek3lw5T01kHmCG8rg==", "devOptional": true, "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.22.0", - "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", - "@prisma/fetch-engine": "5.22.0", - "@prisma/get-platform": "5.22.0" + "@prisma/debug": "5.18.0", + "@prisma/engines-version": "5.18.0-25.4c784e32044a8a016d99474bd02a3b6123742169", + "@prisma/fetch-engine": "5.18.0", + "@prisma/get-platform": "5.18.0" } }, "node_modules/@prisma/engines-version": { - "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz", - "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==", - "devOptional": true + "version": "5.18.0-25.4c784e32044a8a016d99474bd02a3b6123742169", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.18.0-25.4c784e32044a8a016d99474bd02a3b6123742169.tgz", + "integrity": "sha512-a/+LpJj8vYU3nmtkg+N3X51ddbt35yYrRe8wqHTJtYQt7l1f8kjIBcCs6sHJvodW/EK5XGvboOiwm47fmNrbgg==", + "devOptional": true, + "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz", - "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.18.0.tgz", + "integrity": "sha512-I/3u0x2n31rGaAuBRx2YK4eB7R/1zCuayo2DGwSpGyrJWsZesrV7QVw7ND0/Suxeo/vLkJ5OwuBqHoCxvTHpOg==", "devOptional": true, + "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.22.0", - "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", - "@prisma/get-platform": "5.22.0" + "@prisma/debug": "5.18.0", + "@prisma/engines-version": "5.18.0-25.4c784e32044a8a016d99474bd02a3b6123742169", + "@prisma/get-platform": "5.18.0" } }, "node_modules/@prisma/get-platform": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz", - "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.18.0.tgz", + "integrity": "sha512-Tk+m7+uhqcKDgnMnFN0lRiH7Ewea0OEsZZs9pqXa7i3+7svS3FSCqDBCaM9x5fmhhkufiG0BtunJVDka+46DlA==", "devOptional": true, + "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.22.0" + "@prisma/debug": "5.18.0" } }, "node_modules/@rtsao/scc": { @@ -7372,22 +7378,20 @@ } }, "node_modules/prisma": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", - "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.18.0.tgz", + "integrity": "sha512-+TrSIxZsh64OPOmaSgVPH7ALL9dfU0jceYaMJXsNrTkFHO7/3RANi5K2ZiPB1De9+KDxCWn7jvRq8y8pvk+o9g==", "devOptional": true, "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@prisma/engines": "5.22.0" + "@prisma/engines": "5.18.0" }, "bin": { "prisma": "build/index.js" }, "engines": { "node": ">=16.13" - }, - "optionalDependencies": { - "fsevents": "2.3.3" } }, "node_modules/prompts": { diff --git a/api/prisma/schema.prisma b/api/prisma/schema.prisma index 05ebbb995..36ce3d374 100644 --- a/api/prisma/schema.prisma +++ b/api/prisma/schema.prisma @@ -190,6 +190,7 @@ model user { // created_datasets dataset_create_log[] alerts_created alert[] owned_import_sources import_source[] + revoked_workflows workflow[] @relation("revoked_workflows") } model user_password { @@ -284,6 +285,8 @@ model workflow { dataset dataset? @relation(fields: [dataset_id], references: [id], onDelete: Cascade) initiator_id Int? initiator user? @relation("initiated_workflows", fields: [initiator_id], references: [id], onDelete: SetNull) + revoked_by_id Int? + revoked_by user? @relation("revoked_workflows", fields: [revoked_by_id], references: [id], onDelete: SetNull) } model metric { diff --git a/api/src/routes/workflows.js b/api/src/routes/workflows.js index 69560f573..e8204b83d 100644 --- a/api/src/routes/workflows.js +++ b/api/src/routes/workflows.js @@ -104,11 +104,15 @@ router.get( }, include: { initiator: true, + revoked_by: true, }, }); - const id_initiator_map = rows.reduce((acc, wf) => { - acc[wf.id] = wf.initiator; + const id_user_map = rows.reduce((acc, wf) => { + acc[wf.id] = { + initiator: wf.initiator, + revoked_by: wf.revoked_by, + }; return acc; }, {}); @@ -122,7 +126,8 @@ router.get( return { ...wf, dataset_id: app_workflow?.dataset_id, - initiator: id_initiator_map[wf.id], + initiator: id_user_map[wf.id]?.initiator, + revoked_by: id_user_map[wf.id]?.revoked_by, }; }), }); @@ -355,7 +360,16 @@ router.get( req.query.last_task_runs, req.query.prev_task_runs, ); - res.json(api_res.data); + + const app_wf = await prisma.workflow.findUnique({ + where: { id: req.params.id }, + include: { revoked_by: true }, + }); + + res.json({ + ...api_res.data, + revoked_by: app_wf?.revoked_by, + }); }, ), ); @@ -367,6 +381,13 @@ router.post( async (req, res, next) => { // #swagger.tags = ['Workflow'] const api_res = await wf_service.pause(req.params.id); + + await prisma.workflow.upsert({ + where: { id: req.params.id }, + update: { revoked_by_id: req.user.id }, + create: { id: req.params.id, revoked_by_id: req.user.id }, + }); + res.json(api_res.data); }, ), diff --git a/api/tests/routes/workflows.test.js b/api/tests/routes/workflows.test.js new file mode 100644 index 000000000..8727e525e --- /dev/null +++ b/api/tests/routes/workflows.test.js @@ -0,0 +1,93 @@ +const { getAuthRequest } = require('../request'); +const prisma = require('../../src/db'); +const wf_service = require('../../src/services/workflow'); + +// Mock the workflow service +jest.mock('../../src/services/workflow'); + +describe('Workflow Revocation API', () => { + let authRequest; + let testUser; + const testWorkflowId = 'test-workflow-123'; + + beforeAll(async () => { + authRequest = await getAuthRequest(); + + // Get a test user (assuming one exists or created by seed) + testUser = await prisma.user.findFirst({ + where: { is_deleted: false } + }); + + if (!testUser) { + throw new Error('No test user found in database. Please run seed.'); + } + }); + + afterAll(async () => { + // Cleanup test data + await prisma.workflow.deleteMany({ + where: { id: testWorkflowId } + }); + }); + + describe('POST /workflows/:id/pause', () => { + it('records the revoking user in the database', async () => { + // Mock successful pause in workflow service + wf_service.pause.mockResolvedValue({ data: { status: 'REVOKED' } }); + + const response = await authRequest.post(`/workflows/${testWorkflowId}/pause`); + + expect(response.status).toBe(200); + + // Verify database record + const dbWorkflow = await prisma.workflow.findUnique({ + where: { id: testWorkflowId }, + include: { revoked_by: true } + }); + + expect(dbWorkflow).toBeDefined(); + expect(dbWorkflow.revoked_by_id).toBeDefined(); + expect(dbWorkflow.revoked_by).toBeDefined(); + }); + }); + + describe('GET /workflows/:id', () => { + it('includes revoked_by details in the response', async () => { + // Mock successful getOne in workflow service + wf_service.getOne.mockResolvedValue({ + data: { + id: testWorkflowId, + status: 'REVOKED' + } + }); + + const response = await authRequest.get(`/workflows/${testWorkflowId}`); + + expect(response.status).toBe(200); + expect(response.body.revoked_by).toBeDefined(); + expect(response.body.revoked_by.username).toBeDefined(); + }); + }); + + describe('GET /workflows', () => { + it('includes revoked_by details in the list response', async () => { + // Mock successful getAll in workflow service + wf_service.getAll.mockResolvedValue({ + data: { + metadata: { total: 1, skip: 0, limit: 10 }, + results: [{ + id: testWorkflowId, + status: 'REVOKED' + }] + } + }); + + const response = await authRequest.get('/workflows'); + + expect(response.status).toBe(200); + const workflow = response.body.results.find(w => w.id === testWorkflowId); + expect(workflow.revoked_by).toBeDefined(); + expect(workflow.revoked_by.username).toBeDefined(); + }); + }); +}); diff --git a/ui/src/components/runs/Workflow.vue b/ui/src/components/runs/Workflow.vue index 32339a9e2..3f4909c3c 100644 --- a/ui/src/components/runs/Workflow.vue +++ b/ui/src/components/runs/Workflow.vue @@ -81,6 +81,19 @@ + + +
+ + + Revoked by: {{ workflow.revoked_by.name }} ({{ workflow.revoked_by.username }}) + + +
diff --git a/ui/src/components/runs/WorkflowCompact.vue b/ui/src/components/runs/WorkflowCompact.vue index c1aab4eba..8d0932060 100644 --- a/ui/src/components/runs/WorkflowCompact.vue +++ b/ui/src/components/runs/WorkflowCompact.vue @@ -41,6 +41,23 @@ {{ workflow.initiator?.username }} )
+ +
+ + + Revoked by: {{ workflow.revoked_by.name }} ({{ + workflow.revoked_by.username + }}) + + +
@@ -51,6 +68,23 @@ {{ workflow.initiator?.username }} )
+ +
+ + + Revoked by: {{ workflow.revoked_by.name }} ({{ + workflow.revoked_by.username + }}) + + +
diff --git a/ui/vite.config.js b/ui/vite.config.js index dc1cc70fa..fefb780ee 100644 --- a/ui/vite.config.js +++ b/ui/vite.config.js @@ -1,4 +1,3 @@ -import fs from "fs"; import { fileURLToPath, URL } from "node:url"; import vue from "@vitejs/plugin-vue"; @@ -86,13 +85,13 @@ export default defineConfig(({ command, mode }) => { host: true, port: 443, - // https://vitejs.dev/config/#server-https - https: { - key: fs.readFileSync("./.cert/key.pem"), - cert: fs.readFileSync("./.cert/cert.pem"), - }, + // // https://vitejs.dev/config/#server-https + // https: { + // key: fs.readFileSync("./.cert/key.pem"), + // cert: fs.readFileSync("./.cert/cert.pem"), + // }, // just `true` yields errors with Firefox as of 2022.12 - // https: true, + https: false, // https://vitejs.dev/config/#server-proxy // useful when running vite on localhost From a58a960a3918ea16c57dc18147161ed5f7b9fccc Mon Sep 17 00:00:00 2001 From: RahulUbale Date: Sun, 22 Mar 2026 05:30:07 -0400 Subject: [PATCH 2/2] done --- api/package-lock.json | 74 ++++++++++++++++++++----------------------- ui/vite.config.js | 13 ++++---- 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index 28c0db858..e850e223c 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -1378,11 +1378,10 @@ } }, "node_modules/@prisma/client": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.18.0.tgz", - "integrity": "sha512-BWivkLh+af1kqC89zCJYkHsRcyWsM8/JHpsDMM76DjP3ZdEquJhXa4IeX+HkWPnwJ5FanxEJFZZDTWiDs/Kvyw==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz", + "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==", "hasInstallScript": true, - "license": "Apache-2.0", "engines": { "node": ">=16.13" }, @@ -1396,53 +1395,48 @@ } }, "node_modules/@prisma/debug": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.18.0.tgz", - "integrity": "sha512-f+ZvpTLidSo3LMJxQPVgAxdAjzv5OpzAo/eF8qZqbwvgi2F5cTOI9XCpdRzJYA0iGfajjwjOKKrVq64vkxEfUw==", - "devOptional": true, - "license": "Apache-2.0" + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", + "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==", + "devOptional": true }, "node_modules/@prisma/engines": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.18.0.tgz", - "integrity": "sha512-ofmpGLeJ2q2P0wa/XaEgTnX/IsLnvSp/gZts0zjgLNdBhfuj2lowOOPmDcfKljLQUXMvAek3lw5T01kHmCG8rg==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", + "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", "devOptional": true, "hasInstallScript": true, - "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.18.0", - "@prisma/engines-version": "5.18.0-25.4c784e32044a8a016d99474bd02a3b6123742169", - "@prisma/fetch-engine": "5.18.0", - "@prisma/get-platform": "5.18.0" + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/fetch-engine": "5.22.0", + "@prisma/get-platform": "5.22.0" } }, "node_modules/@prisma/engines-version": { - "version": "5.18.0-25.4c784e32044a8a016d99474bd02a3b6123742169", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.18.0-25.4c784e32044a8a016d99474bd02a3b6123742169.tgz", - "integrity": "sha512-a/+LpJj8vYU3nmtkg+N3X51ddbt35yYrRe8wqHTJtYQt7l1f8kjIBcCs6sHJvodW/EK5XGvboOiwm47fmNrbgg==", - "devOptional": true, - "license": "Apache-2.0" + "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz", + "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==", + "devOptional": true }, "node_modules/@prisma/fetch-engine": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.18.0.tgz", - "integrity": "sha512-I/3u0x2n31rGaAuBRx2YK4eB7R/1zCuayo2DGwSpGyrJWsZesrV7QVw7ND0/Suxeo/vLkJ5OwuBqHoCxvTHpOg==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz", + "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==", "devOptional": true, - "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.18.0", - "@prisma/engines-version": "5.18.0-25.4c784e32044a8a016d99474bd02a3b6123742169", - "@prisma/get-platform": "5.18.0" + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/get-platform": "5.22.0" } }, "node_modules/@prisma/get-platform": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.18.0.tgz", - "integrity": "sha512-Tk+m7+uhqcKDgnMnFN0lRiH7Ewea0OEsZZs9pqXa7i3+7svS3FSCqDBCaM9x5fmhhkufiG0BtunJVDka+46DlA==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz", + "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==", "devOptional": true, - "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.18.0" + "@prisma/debug": "5.22.0" } }, "node_modules/@rtsao/scc": { @@ -7378,20 +7372,22 @@ } }, "node_modules/prisma": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.18.0.tgz", - "integrity": "sha512-+TrSIxZsh64OPOmaSgVPH7ALL9dfU0jceYaMJXsNrTkFHO7/3RANi5K2ZiPB1De9+KDxCWn7jvRq8y8pvk+o9g==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", + "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", "devOptional": true, "hasInstallScript": true, - "license": "Apache-2.0", "dependencies": { - "@prisma/engines": "5.18.0" + "@prisma/engines": "5.22.0" }, "bin": { "prisma": "build/index.js" }, "engines": { "node": ">=16.13" + }, + "optionalDependencies": { + "fsevents": "2.3.3" } }, "node_modules/prompts": { diff --git a/ui/vite.config.js b/ui/vite.config.js index fefb780ee..dc1cc70fa 100644 --- a/ui/vite.config.js +++ b/ui/vite.config.js @@ -1,3 +1,4 @@ +import fs from "fs"; import { fileURLToPath, URL } from "node:url"; import vue from "@vitejs/plugin-vue"; @@ -85,13 +86,13 @@ export default defineConfig(({ command, mode }) => { host: true, port: 443, - // // https://vitejs.dev/config/#server-https - // https: { - // key: fs.readFileSync("./.cert/key.pem"), - // cert: fs.readFileSync("./.cert/cert.pem"), - // }, + // https://vitejs.dev/config/#server-https + https: { + key: fs.readFileSync("./.cert/key.pem"), + cert: fs.readFileSync("./.cert/cert.pem"), + }, // just `true` yields errors with Firefox as of 2022.12 - https: false, + // https: true, // https://vitejs.dev/config/#server-proxy // useful when running vite on localhost