From 9804c0db289308003c95d4913bb2017cee66e93d Mon Sep 17 00:00:00 2001 From: Julian van der Horst Date: Wed, 27 May 2026 17:02:13 +0200 Subject: [PATCH 1/9] Ready for testing --- messages/bg-BG.json | 6 +- messages/cs-CZ.json | 6 +- messages/de-DE.json | 6 +- messages/en-US.json | 6 +- messages/es-ES.json | 6 +- messages/fr-FR.json | 6 +- messages/it-IT.json | 6 +- messages/ko-KR.json | 6 +- messages/nb-NO.json | 6 +- messages/nl-NL.json | 6 +- messages/pl-PL.json | 6 +- messages/pt-PT.json | 6 +- messages/ru-RU.json | 6 +- messages/tr-TR.json | 6 +- messages/zh-CN.json | 6 +- messages/zh-TW.json | 6 +- package-lock.json | 37 ++-------- server/db/pg/schema/schema.ts | 3 +- server/db/sqlite/schema/schema.ts | 3 +- server/lib/traefik/getTraefikConfig.ts | 51 +++++++++---- .../private/lib/traefik/getTraefikConfig.ts | 55 +++++++++----- server/routers/resource/getResource.ts | 9 ++- server/routers/resource/updateResource.ts | 71 +++++++++++-------- server/setup/migrationsPg.ts | 4 +- server/setup/migrationsSqlite.ts | 4 +- server/setup/scriptsPg/1.18.5.ts | 30 ++++++++ server/setup/scriptsSqlite/1.18.5.ts | 34 +++++++++ .../resources/proxy/[niceId]/proxy/page.tsx | 48 ++++++++++--- src/components/HealthCheckCredenza.tsx | 4 +- src/components/HealthCheckFormFields.tsx | 4 +- 30 files changed, 308 insertions(+), 145 deletions(-) create mode 100644 server/setup/scriptsPg/1.18.5.ts create mode 100644 server/setup/scriptsSqlite/1.18.5.ts diff --git a/messages/bg-BG.json b/messages/bg-BG.json index 108229942c..60f7225a9a 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -1964,8 +1964,10 @@ "retryAttempts": "Опити за повторно", "expectedResponseCodes": "Очаквани кодове за отговор", "expectedResponseCodesDescription": "HTTP статус код, указващ здравословно състояние. Ако бъде оставено празно, между 200-300 се счита за здравословно.", - "customHeaders": "Персонализирани заглавия", - "customHeadersDescription": "Add custom headers to be sent when proxying requests. One per line in the format Header-Name: value", + "customRequestHeaders": "Персонализирани заглавия", + "customRequestHeadersDescription": "Add custom headers to be sent when proxying requests. One per line in the format Header-Name: value", + "customResponseHeaders": "Custom Response Headers", + "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", "headersValidationError": "Заглавията трябва да бъдат във формат: Име на заглавието: стойност.", "saveHealthCheck": "Запазване на проверка на здравето", "healthCheckSaved": "Проверка на здравето е запазена", diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index 7c118ff295..be171bf091 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -1964,8 +1964,10 @@ "retryAttempts": "Opakovat pokusy", "expectedResponseCodes": "Očekávané kódy odezvy", "expectedResponseCodesDescription": "HTTP kód stavu, který označuje zdravý stav. Ponecháte-li prázdné, 200-300 je považováno za zdravé.", - "customHeaders": "Vlastní záhlaví", - "customHeadersDescription": "Záhlaví oddělená nová řádka: hodnota", + "customRequestHeaders": "Vlastní záhlaví", + "customRequestHeadersDescription": "Záhlaví oddělená nová řádka: hodnota", + "customResponseHeaders": "Custom Response Headers", + "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", "headersValidationError": "Headers must be in the format: Header-Name: value.", "saveHealthCheck": "Uložit kontrolu stavu", "healthCheckSaved": "Kontrola stavu uložena", diff --git a/messages/de-DE.json b/messages/de-DE.json index 11d76dab55..d61bf24b24 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -1964,8 +1964,10 @@ "retryAttempts": "Wiederholungsversuche", "expectedResponseCodes": "Erwartete Antwortcodes", "expectedResponseCodesDescription": "HTTP-Statuscode, der einen gesunden Zustand anzeigt. Wenn leer gelassen, wird 200-300 als gesund angesehen.", - "customHeaders": "Eigene Kopfzeilen", - "customHeadersDescription": "Header neue Zeile getrennt: Header-Name: Wert", + "customRequestHeaders": "Eigene Kopfzeilen", + "customRequestHeadersDescription": "Header neue Zeile getrennt: Header-Name: Wert", + "customResponseHeaders": "Custom Response Headers", + "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", "headersValidationError": "Header müssen im Format Header-Name: Wert sein.", "saveHealthCheck": "Gesundheits-Check speichern", "healthCheckSaved": "Gesundheits-Check gespeichert", diff --git a/messages/en-US.json b/messages/en-US.json index 027d9fc386..829aa06929 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1994,8 +1994,10 @@ "retryAttempts": "Retry Attempts", "expectedResponseCodes": "Expected Response Codes", "expectedResponseCodesDescription": "HTTP status code that indicates healthy status. If left blank, 200-300 is considered healthy.", - "customHeaders": "Custom Request Headers", - "customHeadersDescription": "Request headers sent to the downstream targets. Headers new line separated: Header-Name: value", + "customRequestHeaders": "Custom Request Headers", + "customRequestHeadersDescription": "Request headers sent to the downstream targets. One per line: Header-Name: value", + "customResponseHeaders": "Custom Response Headers", + "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", "headersValidationError": "Headers must be in the format: Header-Name: value", "saveHealthCheck": "Save Health Check", "healthCheckSaved": "Health Check Saved", diff --git a/messages/es-ES.json b/messages/es-ES.json index 9e5b6fc825..2b1e55381f 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -1964,8 +1964,10 @@ "retryAttempts": "Intentos de Reintento", "expectedResponseCodes": "Códigos de respuesta esperados", "expectedResponseCodesDescription": "Código de estado HTTP que indica un estado saludable. Si se deja en blanco, se considera saludable de 200 a 300.", - "customHeaders": "Cabeceras personalizadas", - "customHeadersDescription": "Nueva línea de cabeceras separada: Nombre de cabecera: valor", + "customRequestHeaders": "Cabeceras personalizadas", + "customRequestHeadersDescription": "Nueva línea de cabeceras separada: Nombre de cabecera: valor", + "customResponseHeaders": "Custom Response Headers", + "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", "headersValidationError": "Los encabezados deben estar en el formato: Nombre de cabecera: valor.", "saveHealthCheck": "Guardar Chequeo de Salud", "healthCheckSaved": "Chequeo de Salud Guardado", diff --git a/messages/fr-FR.json b/messages/fr-FR.json index da3350e463..d9a83e655c 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -1964,8 +1964,10 @@ "retryAttempts": "Tentatives de réessai", "expectedResponseCodes": "Codes de réponse attendus", "expectedResponseCodesDescription": "Code de statut HTTP indiquant un état de santé satisfaisant. Si non renseigné, 200-300 est considéré comme satisfaisant.", - "customHeaders": "En-têtes personnalisés", - "customHeadersDescription": "En-têtes séparés par une nouvelle ligne: En-nom: valeur", + "customRequestHeaders": "En-têtes personnalisés", + "customRequestHeadersDescription": "En-têtes séparés par une nouvelle ligne: En-nom: valeur", + "customResponseHeaders": "Custom Response Headers", + "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", "headersValidationError": "Les entêtes doivent être au format : Header-Name: valeur.", "saveHealthCheck": "Sauvegarder la vérification de l'état de santé", "healthCheckSaved": "Vérification de l'état de santé enregistrée", diff --git a/messages/it-IT.json b/messages/it-IT.json index 3ec9c00111..eac3ecde99 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -1964,8 +1964,10 @@ "retryAttempts": "Tentativi di Riprova", "expectedResponseCodes": "Codici di Risposta Attesi", "expectedResponseCodesDescription": "Codice di stato HTTP che indica lo stato di salute. Se lasciato vuoto, considerato sano è compreso tra 200-300.", - "customHeaders": "Intestazioni Personalizzate", - "customHeadersDescription": "Intestazioni nuova riga separate: Intestazione-Nome: valore", + "customRequestHeaders": "Intestazioni Personalizzate", + "customRequestHeadersDescription": "Intestazioni nuova riga separate: Intestazione-Nome: valore", + "customResponseHeaders": "Custom Response Headers", + "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", "headersValidationError": "Le intestazioni devono essere nel formato: Intestazione-Nome: valore.", "saveHealthCheck": "Salva Controllo Salute", "healthCheckSaved": "Controllo Salute Salvato", diff --git a/messages/ko-KR.json b/messages/ko-KR.json index d1bd16382d..76facec3ed 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -1964,8 +1964,10 @@ "retryAttempts": "재시도 횟수", "expectedResponseCodes": "예상 응답 코드", "expectedResponseCodesDescription": "정상 상태를 나타내는 HTTP 상태 코드입니다. 비워 두면 200-300이 정상으로 간주됩니다.", - "customHeaders": "사용자 정의 헤더", - "customHeadersDescription": "헤더는 새 줄로 구분됨: Header-Name: value", + "customRequestHeaders": "사용자 정의 헤더", + "customRequestHeadersDescription": "헤더는 새 줄로 구분됨: Header-Name: value", + "customResponseHeaders": "Custom Response Headers", + "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", "headersValidationError": "헤더는 형식이어야 합니다: 헤더명: 값.", "saveHealthCheck": "상태 확인 저장", "healthCheckSaved": "상태 확인이 저장되었습니다.", diff --git a/messages/nb-NO.json b/messages/nb-NO.json index d76013d166..2aa54d52ab 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -1964,8 +1964,10 @@ "retryAttempts": "Forsøk på nytt", "expectedResponseCodes": "Forventede svarkoder", "expectedResponseCodesDescription": "HTTP-statuskode som indikerer sunn status. Hvis den blir stående tom, regnes 200-300 som sunn.", - "customHeaders": "Egendefinerte topptekster", - "customHeadersDescription": "Overskrifter som er adskilt med linje: Overskriftsnavn: verdi", + "customRequestHeaders": "Egendefinerte topptekster", + "customRequestHeadersDescription": "Overskrifter som er adskilt med linje: Overskriftsnavn: verdi", + "customResponseHeaders": "Custom Response Headers", + "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", "headersValidationError": "Topptekst må være i formatet: header-navn: verdi.", "saveHealthCheck": "Lagre Helsekontroll", "healthCheckSaved": "Helsekontroll Lagret", diff --git a/messages/nl-NL.json b/messages/nl-NL.json index f989db3425..29215eb938 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -1964,8 +1964,10 @@ "retryAttempts": "Herhaal Pogingen", "expectedResponseCodes": "Verwachte Reactiecodes", "expectedResponseCodesDescription": "HTTP-statuscode die gezonde status aangeeft. Indien leeg wordt 200-300 als gezond beschouwd.", - "customHeaders": "Aangepaste headers", - "customHeadersDescription": "Kopregeleinde: Header-Naam: waarde", + "customRequestHeaders": "Aangepaste headers", + "customRequestHeadersDescription": "Kopregeleinde: Header-Naam: waarde", + "customResponseHeaders": "Custom Response Headers", + "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", "headersValidationError": "Headers moeten in het formaat zijn: Header-Naam: waarde.", "saveHealthCheck": "Opslaan Gezondheidscontrole", "healthCheckSaved": "Gezondheidscontrole Opgeslagen", diff --git a/messages/pl-PL.json b/messages/pl-PL.json index 4d801023ba..205b34ded3 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -1964,8 +1964,10 @@ "retryAttempts": "Próby Ponowienia", "expectedResponseCodes": "Oczekiwane Kody Odpowiedzi", "expectedResponseCodesDescription": "Kod statusu HTTP, który wskazuje zdrowy status. Jeśli pozostanie pusty, uznaje się 200-300 za zdrowy.", - "customHeaders": "Niestandardowe nagłówki", - "customHeadersDescription": "Nagłówki oddzielone: Nazwa nagłówka: wartość", + "customRequestHeaders": "Niestandardowe nagłówki", + "customRequestHeadersDescription": "Nagłówki oddzielone: Nazwa nagłówka: wartość", + "customResponseHeaders": "Custom Response Headers", + "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", "headersValidationError": "Nagłówki muszą być w formacie: Nazwa nagłówka: wartość.", "saveHealthCheck": "Zapisz Kontrolę Zdrowia", "healthCheckSaved": "Kontrola Zdrowia Zapisana", diff --git a/messages/pt-PT.json b/messages/pt-PT.json index 0604c1caf8..b3086aa1c3 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -1964,8 +1964,10 @@ "retryAttempts": "Tentativas de Repetição", "expectedResponseCodes": "Códigos de Resposta Esperados", "expectedResponseCodesDescription": "Código de status HTTP que indica estado saudável. Se deixado em branco, 200-300 é considerado saudável.", - "customHeaders": "Cabeçalhos Personalizados", - "customHeadersDescription": "Separados por cabeçalhos da nova linha: Nome do Cabeçalho: valor", + "customRequestHeaders": "Cabeçalhos Personalizados", + "customRequestHeadersDescription": "Separados por cabeçalhos da nova linha: Nome do Cabeçalho: valor", + "customResponseHeaders": "Custom Response Headers", + "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", "headersValidationError": "Cabeçalhos devem estar no formato: Nome do Cabeçalho: valor.", "saveHealthCheck": "Salvar Verificação de Saúde", "healthCheckSaved": "Verificação de Saúde Salva", diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 0f3e489625..556d1d35c1 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -1964,8 +1964,10 @@ "retryAttempts": "Количество попыток повторного запроса", "expectedResponseCodes": "Ожидаемые коды ответов", "expectedResponseCodesDescription": "HTTP-код состояния, указывающий на здоровое состояние. Если оставить пустым, 200-300 считается здоровым.", - "customHeaders": "Пользовательские заголовки", - "customHeadersDescription": "Заголовки новой строки, разделённые: название заголовка: значение", + "customRequestHeaders": "Пользовательские заголовки", + "customRequestHeadersDescription": "Заголовки новой строки, разделённые: название заголовка: значение", + "customResponseHeaders": "Custom Response Headers", + "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", "headersValidationError": "Заголовки должны быть в формате: Название заголовка: значение.", "saveHealthCheck": "Сохранить проверку здоровья", "healthCheckSaved": "Проверка здоровья сохранена", diff --git a/messages/tr-TR.json b/messages/tr-TR.json index e1d965e8e4..3b5a324e2b 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -1964,8 +1964,10 @@ "retryAttempts": "Tekrar Deneme Girişimleri", "expectedResponseCodes": "Beklenen Yanıt Kodları", "expectedResponseCodesDescription": "Sağlıklı durumu gösteren HTTP durum kodu. Boş bırakılırsa, 200-300 arası sağlıklı kabul edilir.", - "customHeaders": "Özel Başlıklar", - "customHeadersDescription": "Başlıklar yeni satırla ayrılmış: Başlık-Adı: değer", + "customRequestHeaders": "Özel Başlıklar", + "customRequestHeadersDescription": "Başlıklar yeni satırla ayrılmış: Başlık-Adı: değer", + "customResponseHeaders": "Custom Response Headers", + "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", "headersValidationError": "Başlıklar şu formatta olmalıdır: Başlık-Adı: değer.", "saveHealthCheck": "Sağlık Kontrolünü Kaydet", "healthCheckSaved": "Sağlık Kontrolü Kaydedildi", diff --git a/messages/zh-CN.json b/messages/zh-CN.json index a23647dbad..dd01d78f84 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -1964,8 +1964,10 @@ "retryAttempts": "重试次数", "expectedResponseCodes": "期望响应代码", "expectedResponseCodesDescription": "HTTP 状态码表示健康状态。如留空,200-300 被视为健康。", - "customHeaders": "自定义标题", - "customHeadersDescription": "头部新行分隔:头部名称:值", + "customRequestHeaders": "自定义标题", + "customRequestHeadersDescription": "头部新行分隔:头部名称:值", + "customResponseHeaders": "Custom Response Headers", + "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", "headersValidationError": "头部必须是格式:头部名称:值。", "saveHealthCheck": "保存健康检查", "healthCheckSaved": "健康检查已保存", diff --git a/messages/zh-TW.json b/messages/zh-TW.json index 5329625934..e6c4a95f22 100644 --- a/messages/zh-TW.json +++ b/messages/zh-TW.json @@ -1555,8 +1555,10 @@ "retryAttempts": "重試次數", "expectedResponseCodes": "期望響應代碼", "expectedResponseCodesDescription": "HTTP 狀態碼表示健康狀態。如留空,200-300 被視為健康。", - "customHeaders": "自訂 Headers", - "customHeadersDescription": "Header 斷行分隔:Header 名稱:值", + "customRequestHeaders": "自訂 Headers", + "customRequestHeadersDescription": "Header 斷行分隔:Header 名稱:值", + "customResponseHeaders": "Custom Response Headers", + "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", "headersValidationError": "Header 必須是格式:Header 名稱:值。", "saveHealthCheck": "保存健康檢查", "healthCheckSaved": "健康檢查已保存", diff --git a/package-lock.json b/package-lock.json index 8c241554ab..d06ae5554c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1058,7 +1058,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -3034,7 +3033,6 @@ "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -6981,7 +6979,6 @@ "resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.1.6.tgz", "integrity": "sha512-TYqkioRS45wTR5il3dYk/SbUjjEdhSwh9BtRNB99qNH1pXAwA45H7rAuxehiu8iJQJH0IyIr+6n62gBz9ezmsw==", "license": "MIT", - "peer": true, "engines": { "node": ">=20.0.0" }, @@ -8442,7 +8439,6 @@ "version": "5.90.21", "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz", "integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==", - "peer": true, "dependencies": { "@tanstack/query-core": "5.90.20" }, @@ -8558,7 +8554,6 @@ "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -8906,7 +8901,6 @@ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -9002,7 +8996,6 @@ "integrity": "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -9030,7 +9023,6 @@ "integrity": "sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "pg-protocol": "*", @@ -9056,7 +9048,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -9067,7 +9058,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -9154,7 +9144,8 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/@types/ws": { "version": "8.18.1", @@ -9228,7 +9219,6 @@ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -9702,7 +9692,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -10152,7 +10141,6 @@ "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/types": "^7.26.0" } @@ -10224,7 +10212,6 @@ "integrity": "sha512-Ba0KR+Fzxh2jDRhdg6TSH0SJGzb8C0aBY4hR8w8madIdIzzC6Y1+kx5qR6eS1Z+Gy20h6ZU28aeyg0z1VIrShQ==", "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" @@ -10353,7 +10340,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -11260,7 +11246,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -11701,6 +11686,7 @@ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz", "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==", "license": "(MPL-2.0 OR Apache-2.0)", + "peer": true, "engines": { "node": ">=20" }, @@ -12335,7 +12321,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -12421,7 +12406,6 @@ "integrity": "sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -12558,7 +12542,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -12952,7 +12935,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -15370,6 +15352,7 @@ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", "license": "MIT", + "peer": true, "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" @@ -15380,6 +15363,7 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -15468,7 +15452,6 @@ "resolved": "https://registry.npmjs.org/next/-/next-15.5.15.tgz", "integrity": "sha512-VSqCrJwtLVGwAVE0Sb/yikrQfkwkZW9p+lL/J4+xe+G3ZA+QnWPqgcfH1tDUEuk9y+pthzzVFp4L/U8JerMfMQ==", "license": "MIT", - "peer": true, "dependencies": { "@next/env": "15.5.15", "@swc/helpers": "0.5.15", @@ -16428,7 +16411,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.12.0", "pg-pool": "^3.13.0", @@ -16936,7 +16918,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -16968,7 +16949,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -17261,7 +17241,6 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.2.tgz", "integrity": "sha512-1CHvcDYzuRUNOflt4MOq3ZM46AronNJtQ1S7tnX6YN4y72qhgiUItpacZUAQ0TyWYci3yz1X+rXaSxiuEm86PA==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" }, @@ -18723,8 +18702,7 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.3.2", @@ -19199,7 +19177,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -19627,7 +19604,6 @@ "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", "license": "MIT", - "peer": true, "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", @@ -19834,7 +19810,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index 58e78735c5..e8784a9992 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -144,7 +144,8 @@ export const resources = pgTable("resources", { skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, { onDelete: "set null" }), - headers: text("headers"), // comma-separated list of headers to add to the request + requestHeaders: text("requestHeaders"), + responseHeaders: text("responseHeaders"), proxyProtocol: boolean("proxyProtocol").notNull().default(false), proxyProtocolVersion: integer("proxyProtocolVersion").default(1), diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts index e3e83d222d..54ac679695 100644 --- a/server/db/sqlite/schema/schema.ts +++ b/server/db/sqlite/schema/schema.ts @@ -161,7 +161,8 @@ export const resources = sqliteTable("resources", { skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, { onDelete: "set null" }), - headers: text("headers"), // comma-separated list of headers to add to the request + requestHeaders: text("requestHeaders"), + responseHeaders: text("responseHeaders"), proxyProtocol: integer("proxyProtocol", { mode: "boolean" }) .notNull() .default(false), diff --git a/server/lib/traefik/getTraefikConfig.ts b/server/lib/traefik/getTraefikConfig.ts index 7379cad7f8..ee5c25ea96 100644 --- a/server/lib/traefik/getTraefikConfig.ts +++ b/server/lib/traefik/getTraefikConfig.ts @@ -65,7 +65,8 @@ export async function getTraefikConfig( tlsServerName: resources.tlsServerName, setHostHeader: resources.setHostHeader, enableProxy: resources.enableProxy, - headers: resources.headers, + requestHeaders: resources.requestHeaders, + responseHeaders: resources.responseHeaders, proxyProtocol: resources.proxyProtocol, proxyProtocolVersion: resources.proxyProtocolVersion, @@ -177,7 +178,8 @@ export async function getTraefikConfig( setHostHeader: row.setHostHeader, enableProxy: row.enableProxy, targets: [], - headers: row.headers, + requestHeaders: row.requestHeaders, + responseHeaders: row.responseHeaders, proxyProtocol: row.proxyProtocol, proxyProtocolVersion: row.proxyProtocolVersion ?? 1, path: row.path, // the targets will all have the same path @@ -364,38 +366,59 @@ export async function getTraefikConfig( } // Handle custom headers middleware - if (resource.headers || resource.setHostHeader) { - const headersObj: { [key: string]: string } = {}; + if (resource.requestHeaders || resource.responseHeaders || resource.setHostHeader) { + const requestHeadersObj: { [key: string]: string } = {}; + const responseHeadersObj: { [key: string]: string } = {}; - if (resource.headers) { - let headersArr: { name: string; value: string }[] = []; + if (resource.requestHeaders) { + let requestHeadersArr: { name: string; value: string }[] = []; try { - headersArr = JSON.parse(resource.headers) as { + requestHeadersArr = JSON.parse(resource.requestHeaders) as { name: string; value: string; }[]; } catch (e) { logger.warn( - `Failed to parse headers for resource ${resource.resourceId}: ${e}` + `Failed to parse requestHeaders for resource ${resource.resourceId}: ${e}` ); } - - headersArr.forEach((header) => { - headersObj[header.name] = header.value; + requestHeadersArr.forEach((header) => { + requestHeadersObj[header.name] = header.value; }); } if (resource.setHostHeader) { - headersObj["Host"] = resource.setHostHeader; + requestHeadersObj["Host"] = resource.setHostHeader; } - if (Object.keys(headersObj).length > 0) { + if (resource.responseHeaders) { + let responseHeadersArr: { name: string; value: string }[] = []; + try { + responseHeadersArr = JSON.parse(resource.responseHeaders) as { + name: string; + value: string; + }[]; + } catch (e) { + logger.warn( + `Failed to parse responseHeaders for resource ${resource.resourceId}: ${e}` + ); + } + responseHeadersArr.forEach((header) => { + responseHeadersObj[header.name] = header.value; + }); + } + + const hasRequestHeaders = Object.keys(requestHeadersObj).length > 0; + const hasResponseHeaders = Object.keys(responseHeadersObj).length > 0; + + if (hasRequestHeaders || hasResponseHeaders) { if (!config_output.http.middlewares) { config_output.http.middlewares = {}; } config_output.http.middlewares[headersMiddlewareName] = { headers: { - customRequestHeaders: headersObj + ...(hasRequestHeaders && { customRequestHeaders: requestHeadersObj }), + ...(hasResponseHeaders && { customResponseHeaders: responseHeadersObj }) } }; diff --git a/server/private/lib/traefik/getTraefikConfig.ts b/server/private/lib/traefik/getTraefikConfig.ts index 481192fb54..e35544dc90 100644 --- a/server/private/lib/traefik/getTraefikConfig.ts +++ b/server/private/lib/traefik/getTraefikConfig.ts @@ -105,7 +105,8 @@ export async function getTraefikConfig( tlsServerName: resources.tlsServerName, setHostHeader: resources.setHostHeader, enableProxy: resources.enableProxy, - headers: resources.headers, + requestHeaders: resources.requestHeaders, + responseHeaders: resources.responseHeaders, proxyProtocol: resources.proxyProtocol, proxyProtocolVersion: resources.proxyProtocolVersion, wildcard: resources.wildcard, @@ -237,7 +238,8 @@ export async function getTraefikConfig( setHostHeader: row.setHostHeader, enableProxy: row.enableProxy, targets: [], - headers: row.headers, + requestHeaders: row.requestHeaders, + responseHeaders: row.responseHeaders, proxyProtocol: row.proxyProtocol, proxyProtocolVersion: row.proxyProtocolVersion ?? 1, path: row.path, // the targets will all have the same path @@ -648,40 +650,59 @@ export async function getTraefikConfig( } } - if (resource.headers || resource.setHostHeader) { - // if there are headers, parse them into an object - const headersObj: { [key: string]: string } = {}; - if (resource.headers) { - let headersArr: { name: string; value: string }[] = []; + if (resource.requestHeaders || resource.responseHeaders || resource.setHostHeader) { + const requestHeadersObj: { [key: string]: string } = {}; + const responseHeadersObj: { [key: string]: string } = {}; + + if (resource.requestHeaders) { + let requestHeadersArr: { name: string; value: string }[] = []; try { - headersArr = JSON.parse(resource.headers) as { + requestHeadersArr = JSON.parse(resource.requestHeaders) as { name: string; value: string; }[]; } catch (e) { logger.warn( - `Failed to parse headers for resource ${resource.resourceId}: ${e}` + `Failed to parse requestHeaders for resource ${resource.resourceId}: ${e}` ); } - - headersArr.forEach((header) => { - headersObj[header.name] = header.value; + requestHeadersArr.forEach((header) => { + requestHeadersObj[header.name] = header.value; }); } if (resource.setHostHeader) { - headersObj["Host"] = resource.setHostHeader; + requestHeadersObj["Host"] = resource.setHostHeader; + } + + if (resource.responseHeaders) { + let responseHeadersArr: { name: string; value: string }[] = []; + try { + responseHeadersArr = JSON.parse(resource.responseHeaders) as { + name: string; + value: string; + }[]; + } catch (e) { + logger.warn( + `Failed to parse responseHeaders for resource ${resource.resourceId}: ${e}` + ); + } + responseHeadersArr.forEach((header) => { + responseHeadersObj[header.name] = header.value; + }); } - // check if the object is not empty - if (Object.keys(headersObj).length > 0) { - // Add the headers middleware + const hasRequestHeaders = Object.keys(requestHeadersObj).length > 0; + const hasResponseHeaders = Object.keys(responseHeadersObj).length > 0; + + if (hasRequestHeaders || hasResponseHeaders) { if (!config_output.http.middlewares) { config_output.http.middlewares = {}; } config_output.http.middlewares[headersMiddlewareName] = { headers: { - customRequestHeaders: headersObj + ...(hasRequestHeaders && { customRequestHeaders: requestHeadersObj }), + ...(hasResponseHeaders && { customResponseHeaders: responseHeadersObj }) } }; diff --git a/server/routers/resource/getResource.ts b/server/routers/resource/getResource.ts index 7a52c0a852..bbd58320c7 100644 --- a/server/routers/resource/getResource.ts +++ b/server/routers/resource/getResource.ts @@ -105,9 +105,12 @@ export async function getResource( return response(res, { data: { ...resource, - headers: resource.headers - ? JSON.parse(resource.headers) - : resource.headers + requestHeaders: resource.requestHeaders + ? JSON.parse(resource.requestHeaders) + : resource.requestHeaders, + responseHeaders: resource.responseHeaders + ? JSON.parse(resource.responseHeaders) + : resource.responseHeaders }, success: true, error: false, diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index 0a7052dce8..fc8416282d 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -58,7 +58,11 @@ const updateHttpResourceBodySchema = z tlsServerName: z.string().nullable().optional(), setHostHeader: z.string().nullable().optional(), skipToIdpId: z.int().positive().nullable().optional(), - headers: z + requestHeaders: z + .array(z.strictObject({ name: z.string(), value: z.string() })) + .nullable() + .optional(), + responseHeaders: z .array(z.strictObject({ name: z.string(), value: z.string() })) .nullable() .optional(), @@ -111,12 +115,12 @@ const updateHttpResourceBodySchema = z ) .refine( (data) => { - if (data.headers) { - // HTTP header names must be valid token characters (RFC 7230) - const validHeaderName = /^[a-zA-Z0-9!#$%&'*+\-.^_`|~]+$/; - return data.headers.every((h) => validHeaderName.test(h.name)); - } - return true; + const validHeaderName = /^[a-zA-Z0-9!#$%&'*+\-.^_`|~]+$/; + const allHeaders = [ + ...(data.requestHeaders ?? []), + ...(data.responseHeaders ?? []) + ]; + return allHeaders.every((h) => validHeaderName.test(h.name)); }, { error: "Header names may only contain valid HTTP token characters (letters, digits, and !#$%&'*+-.^_`|~)." @@ -124,14 +128,12 @@ const updateHttpResourceBodySchema = z ) .refine( (data) => { - if (data.headers) { - // HTTP header values must be visible ASCII or horizontal whitespace, no control chars (RFC 7230) - const validHeaderValue = /^[\t\x20-\x7E]*$/; - return data.headers.every((h) => - validHeaderValue.test(h.value) - ); - } - return true; + const validHeaderValue = /^[\t\x20-\x7E]*$/; + const allHeaders = [ + ...(data.requestHeaders ?? []), + ...(data.responseHeaders ?? []) + ]; + return allHeaders.every((h) => validHeaderValue.test(h.value)); }, { error: "Header values may only contain printable ASCII characters and horizontal whitespace." @@ -139,16 +141,16 @@ const updateHttpResourceBodySchema = z ) .refine( (data) => { - if (data.headers) { - // Reject Traefik template syntax {{word}} in names or values - const templatePattern = /\{\{[^}]+\}\}/; - return data.headers.every( - (h) => - !templatePattern.test(h.name) && - !templatePattern.test(h.value) - ); - } - return true; + const templatePattern = /\{\{[^}]+\}\}/; + const allHeaders = [ + ...(data.requestHeaders ?? []), + ...(data.responseHeaders ?? []) + ]; + return allHeaders.every( + (h) => + !templatePattern.test(h.name) && + !templatePattern.test(h.value) + ); }, { error: "Header names and values must not contain template expressions such as {{value}}." @@ -467,11 +469,18 @@ async function updateHttpResource( } } - let headers = undefined; - if (updateData.headers) { - headers = JSON.stringify(updateData.headers); - } else if (updateData.headers === null) { - headers = null; + let requestHeaders = undefined; + if (updateData.requestHeaders) { + requestHeaders = JSON.stringify(updateData.requestHeaders); + } else if (updateData.requestHeaders === null) { + requestHeaders = null; + } + + let responseHeaders = undefined; + if (updateData.responseHeaders) { + responseHeaders = JSON.stringify(updateData.responseHeaders); + } else if (updateData.responseHeaders === null) { + responseHeaders = null; } const isLicensed = await isLicensedOrSubscribed( @@ -488,7 +497,7 @@ async function updateHttpResource( const updatedResource = await db .update(resources) - .set({ ...updateData, headers }) + .set({ ...updateData, requestHeaders, responseHeaders }) .where(eq(resources.resourceId, resource.resourceId)) .returning(); diff --git a/server/setup/migrationsPg.ts b/server/setup/migrationsPg.ts index 0b8af06bca..1a7d530c47 100644 --- a/server/setup/migrationsPg.ts +++ b/server/setup/migrationsPg.ts @@ -25,6 +25,7 @@ import m16 from "./scriptsPg/1.17.0"; import m17 from "./scriptsPg/1.18.0"; import m18 from "./scriptsPg/1.18.3"; import m19 from "./scriptsPg/1.18.4"; +import m20 from "./scriptsPg/1.18.5"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -49,7 +50,8 @@ const migrations = [ { version: "1.17.0", run: m16 }, { version: "1.18.0", run: m17 }, { version: "1.18.3", run: m18 }, - { version: "1.18.4", run: m19 } + { version: "1.18.4", run: m19 }, + { version: "1.18.5", run: m20 } // Add new migrations here as they are created ] as { version: string; diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index 837b039f7d..75f414c75d 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -43,6 +43,7 @@ import m37 from "./scriptsSqlite/1.17.0"; import m38 from "./scriptsSqlite/1.18.0"; import m39 from "./scriptsSqlite/1.18.3"; import m40 from "./scriptsSqlite/1.18.4"; +import m41 from "./scriptsSqlite/1.18.5"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -83,7 +84,8 @@ const migrations = [ { version: "1.17.0", run: m37 }, { version: "1.18.0", run: m38 }, { version: "1.18.3", run: m39 }, - { version: "1.18.4", run: m40 } + { version: "1.18.4", run: m40 }, + { version: "1.18.5", run: m41 } // Add new migrations here as they are created ] as const; diff --git a/server/setup/scriptsPg/1.18.5.ts b/server/setup/scriptsPg/1.18.5.ts new file mode 100644 index 0000000000..5bceb41cf2 --- /dev/null +++ b/server/setup/scriptsPg/1.18.5.ts @@ -0,0 +1,30 @@ +import { db } from "@server/db/pg/driver"; +import { sql } from "drizzle-orm"; + +const version = "1.18.5"; + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + try { + await db.execute(sql`BEGIN`); + + await db.execute(sql` + ALTER TABLE "resources" RENAME COLUMN "headers" TO "requestHeaders"; + `); + + await db.execute(sql` + ALTER TABLE "resources" ADD COLUMN "responseHeaders" text; + `); + + await db.execute(sql`COMMIT`); + console.log("Migrated database"); + } catch (e) { + await db.execute(sql`ROLLBACK`); + console.log("Unable to migrate database"); + console.log(e); + throw e; + } + + console.log(`${version} migration complete`); +} diff --git a/server/setup/scriptsSqlite/1.18.5.ts b/server/setup/scriptsSqlite/1.18.5.ts new file mode 100644 index 0000000000..2edd523f09 --- /dev/null +++ b/server/setup/scriptsSqlite/1.18.5.ts @@ -0,0 +1,34 @@ +import { APP_PATH } from "@server/lib/consts"; +import Database from "better-sqlite3"; +import path from "path"; + +const version = "1.18.5"; + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + const location = path.join(APP_PATH, "db", "db.sqlite"); + const db = new Database(location); + + try { + db.pragma("foreign_keys = OFF"); + + db.transaction(() => { + db.prepare( + `ALTER TABLE 'resources' RENAME COLUMN 'headers' TO 'requestHeaders';` + ).run(); + db.prepare( + `ALTER TABLE 'resources' ADD 'responseHeaders' text;` + ).run(); + })(); + + db.pragma("foreign_keys = ON"); + + console.log("Migrated database"); + } catch (e) { + console.log("Failed to migrate db:", e); + throw e; + } + + console.log(`${version} migration complete`); +} diff --git a/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx b/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx index 823c0f957e..fd491354b8 100644 --- a/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx @@ -1097,7 +1097,10 @@ function ProxyResourceHttpForm({ message: t("proxyErrorInvalidHeader") } ), - headers: z + requestHeaders: z + .array(z.object({ name: z.string(), value: z.string() })) + .nullable(), + responseHeaders: z .array(z.object({ name: z.string(), value: z.string() })) .nullable(), proxyProtocol: z.boolean().optional(), @@ -1108,7 +1111,8 @@ function ProxyResourceHttpForm({ resolver: zodResolver(proxySettingsSchema), defaultValues: { setHostHeader: resource.setHostHeader || "", - headers: resource.headers, + requestHeaders: resource.requestHeaders, + responseHeaders: resource.responseHeaders, proxyProtocol: resource.proxyProtocol || false, proxyProtocolVersion: resource.proxyProtocolVersion || 1 } @@ -1148,7 +1152,8 @@ function ProxyResourceHttpForm({ ssl: tlsData.ssl, tlsServerName: tlsData.tlsServerName || null, setHostHeader: proxyData.setHostHeader || null, - headers: proxyData.headers || null + requestHeaders: proxyData.requestHeaders || null, + responseHeaders: proxyData.responseHeaders || null }; // Single API call to update all settings @@ -1161,7 +1166,8 @@ function ProxyResourceHttpForm({ ssl: tlsData.ssl, tlsServerName: tlsData.tlsServerName || null, setHostHeader: proxyData.setHostHeader || null, - headers: proxyData.headers || null + requestHeaders: proxyData.requestHeaders || null, + responseHeaders: proxyData.responseHeaders || null }); toast({ @@ -1307,11 +1313,35 @@ function ProxyResourceHttpForm({ /> ( + + + {t("customRequestHeaders")} + + + { + field.onChange(value); + }} + rows={4} + /> + + + {t("customRequestHeadersDescription")} + + + + )} + /> + ( - {t("customHeaders")} + {t("customResponseHeaders")} - {t("customHeadersDescription")} + {t("customResponseHeadersDescription")} @@ -1369,7 +1399,7 @@ function ProxyResourceProtocolForm({ message: t("proxyErrorInvalidHeader") } ), - headers: z + requestHeaders: z .array(z.object({ name: z.string(), value: z.string() })) .nullable(), proxyProtocol: z.boolean().optional(), @@ -1380,7 +1410,7 @@ function ProxyResourceProtocolForm({ resolver: zodResolver(proxySettingsSchema), defaultValues: { setHostHeader: resource.setHostHeader || "", - headers: resource.headers, + requestHeaders: resource.requestHeaders, proxyProtocol: resource.proxyProtocol || false, proxyProtocolVersion: resource.proxyProtocolVersion || 1 } diff --git a/src/components/HealthCheckCredenza.tsx b/src/components/HealthCheckCredenza.tsx index 0360a15e78..2bd7a27b1c 100644 --- a/src/components/HealthCheckCredenza.tsx +++ b/src/components/HealthCheckCredenza.tsx @@ -1317,7 +1317,7 @@ export function HealthCheckCredenza(props: HealthCheckCredenzaProps) { {t( - "customHeaders" + "customRequestHeaders" )} @@ -1341,7 +1341,7 @@ export function HealthCheckCredenza(props: HealthCheckCredenzaProps) { {t( - "customHeadersDescription" + "customRequestHeadersDescription" )} diff --git a/src/components/HealthCheckFormFields.tsx b/src/components/HealthCheckFormFields.tsx index 6f5d528dbc..5b43e8a598 100644 --- a/src/components/HealthCheckFormFields.tsx +++ b/src/components/HealthCheckFormFields.tsx @@ -733,7 +733,7 @@ export function HealthCheckFormFields({ render={({ field }) => ( - {t("customHeaders")} + {t("customRequestHeaders")} {t( - "customHeadersDescription" + "customRequestHeadersDescription" )} From c8357e8653d825c266e41d6a6094018713943daa Mon Sep 17 00:00:00 2001 From: Julian van der Horst Date: Wed, 27 May 2026 18:03:58 +0200 Subject: [PATCH 2/9] tested and translated! --- messages/bg-BG.json | 8 ++++---- messages/cs-CZ.json | 8 ++++---- messages/de-DE.json | 8 ++++---- messages/es-ES.json | 8 ++++---- messages/fr-FR.json | 8 ++++---- messages/it-IT.json | 8 ++++---- messages/ko-KR.json | 8 ++++---- messages/nb-NO.json | 8 ++++---- messages/nl-NL.json | 8 ++++---- messages/pl-PL.json | 8 ++++---- messages/pt-PT.json | 8 ++++---- messages/ru-RU.json | 8 ++++---- messages/tr-TR.json | 8 ++++---- messages/zh-CN.json | 8 ++++---- messages/zh-TW.json | 8 ++++---- 15 files changed, 60 insertions(+), 60 deletions(-) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index 60f7225a9a..9b6dd6986c 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -1964,10 +1964,10 @@ "retryAttempts": "Опити за повторно", "expectedResponseCodes": "Очаквани кодове за отговор", "expectedResponseCodesDescription": "HTTP статус код, указващ здравословно състояние. Ако бъде оставено празно, между 200-300 се счита за здравословно.", - "customRequestHeaders": "Персонализирани заглавия", - "customRequestHeadersDescription": "Add custom headers to be sent when proxying requests. One per line in the format Header-Name: value", - "customResponseHeaders": "Custom Response Headers", - "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", + "customRequestHeaders": "Персонализирани заглавия на заявката", + "customRequestHeadersDescription": "Заглавия на заявката, изпратени до целевите сървъри. По едно на ред: Заглавие-Име: стойност", + "customResponseHeaders": "Персонализирани заглавия на отговора", + "customResponseHeadersDescription": "Заглавия на отговора, върнати на клиента. По едно на ред: Заглавие-Име: стойност", "headersValidationError": "Заглавията трябва да бъдат във формат: Име на заглавието: стойност.", "saveHealthCheck": "Запазване на проверка на здравето", "healthCheckSaved": "Проверка на здравето е запазена", diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index be171bf091..0effc0c831 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -1964,10 +1964,10 @@ "retryAttempts": "Opakovat pokusy", "expectedResponseCodes": "Očekávané kódy odezvy", "expectedResponseCodesDescription": "HTTP kód stavu, který označuje zdravý stav. Ponecháte-li prázdné, 200-300 je považováno za zdravé.", - "customRequestHeaders": "Vlastní záhlaví", - "customRequestHeadersDescription": "Záhlaví oddělená nová řádka: hodnota", - "customResponseHeaders": "Custom Response Headers", - "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", + "customRequestHeaders": "Vlastní záhlaví požadavku", + "customRequestHeadersDescription": "Záhlaví požadavku odeslaná do cílů. Jedno na řádek: Název-záhlaví: hodnota", + "customResponseHeaders": "Vlastní záhlaví odpovědi", + "customResponseHeadersDescription": "Záhlaví odpovědi vrácená klientovi. Jedno na řádek: Název-záhlaví: hodnota", "headersValidationError": "Headers must be in the format: Header-Name: value.", "saveHealthCheck": "Uložit kontrolu stavu", "healthCheckSaved": "Kontrola stavu uložena", diff --git a/messages/de-DE.json b/messages/de-DE.json index d61bf24b24..c009d2fd9c 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -1964,10 +1964,10 @@ "retryAttempts": "Wiederholungsversuche", "expectedResponseCodes": "Erwartete Antwortcodes", "expectedResponseCodesDescription": "HTTP-Statuscode, der einen gesunden Zustand anzeigt. Wenn leer gelassen, wird 200-300 als gesund angesehen.", - "customRequestHeaders": "Eigene Kopfzeilen", - "customRequestHeadersDescription": "Header neue Zeile getrennt: Header-Name: Wert", - "customResponseHeaders": "Custom Response Headers", - "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", + "customRequestHeaders": "Eigene Anfrage-Header", + "customRequestHeadersDescription": "Anfrage-Header, die an die Ziele gesendet werden. Eine pro Zeile: Header-Name: Wert", + "customResponseHeaders": "Eigene Antwort-Header", + "customResponseHeadersDescription": "Antwort-Header, die an den Client zurückgesendet werden. Eine pro Zeile: Header-Name: Wert", "headersValidationError": "Header müssen im Format Header-Name: Wert sein.", "saveHealthCheck": "Gesundheits-Check speichern", "healthCheckSaved": "Gesundheits-Check gespeichert", diff --git a/messages/es-ES.json b/messages/es-ES.json index 2b1e55381f..08ba730402 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -1964,10 +1964,10 @@ "retryAttempts": "Intentos de Reintento", "expectedResponseCodes": "Códigos de respuesta esperados", "expectedResponseCodesDescription": "Código de estado HTTP que indica un estado saludable. Si se deja en blanco, se considera saludable de 200 a 300.", - "customRequestHeaders": "Cabeceras personalizadas", - "customRequestHeadersDescription": "Nueva línea de cabeceras separada: Nombre de cabecera: valor", - "customResponseHeaders": "Custom Response Headers", - "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", + "customRequestHeaders": "Cabeceras de solicitud personalizadas", + "customRequestHeadersDescription": "Cabeceras de solicitud enviadas a los destinos. Una por línea: Nombre-Cabecera: valor", + "customResponseHeaders": "Cabeceras de respuesta personalizadas", + "customResponseHeadersDescription": "Cabeceras de respuesta devueltas al cliente. Una por línea: Nombre-Cabecera: valor", "headersValidationError": "Los encabezados deben estar en el formato: Nombre de cabecera: valor.", "saveHealthCheck": "Guardar Chequeo de Salud", "healthCheckSaved": "Chequeo de Salud Guardado", diff --git a/messages/fr-FR.json b/messages/fr-FR.json index d9a83e655c..194e798ab4 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -1964,10 +1964,10 @@ "retryAttempts": "Tentatives de réessai", "expectedResponseCodes": "Codes de réponse attendus", "expectedResponseCodesDescription": "Code de statut HTTP indiquant un état de santé satisfaisant. Si non renseigné, 200-300 est considéré comme satisfaisant.", - "customRequestHeaders": "En-têtes personnalisés", - "customRequestHeadersDescription": "En-têtes séparés par une nouvelle ligne: En-nom: valeur", - "customResponseHeaders": "Custom Response Headers", - "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", + "customRequestHeaders": "En-têtes de requête personnalisés", + "customRequestHeadersDescription": "En-têtes de requête envoyés aux cibles. Un par ligne : Nom-En-tête : valeur", + "customResponseHeaders": "En-têtes de réponse personnalisés", + "customResponseHeadersDescription": "En-têtes de réponse renvoyés au client. Un par ligne : Nom-En-tête : valeur", "headersValidationError": "Les entêtes doivent être au format : Header-Name: valeur.", "saveHealthCheck": "Sauvegarder la vérification de l'état de santé", "healthCheckSaved": "Vérification de l'état de santé enregistrée", diff --git a/messages/it-IT.json b/messages/it-IT.json index eac3ecde99..7e8cd68d7d 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -1964,10 +1964,10 @@ "retryAttempts": "Tentativi di Riprova", "expectedResponseCodes": "Codici di Risposta Attesi", "expectedResponseCodesDescription": "Codice di stato HTTP che indica lo stato di salute. Se lasciato vuoto, considerato sano è compreso tra 200-300.", - "customRequestHeaders": "Intestazioni Personalizzate", - "customRequestHeadersDescription": "Intestazioni nuova riga separate: Intestazione-Nome: valore", - "customResponseHeaders": "Custom Response Headers", - "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", + "customRequestHeaders": "Intestazioni di richiesta personalizzate", + "customRequestHeadersDescription": "Intestazioni di richiesta inviate ai target. Una per riga: Nome-Intestazione: valore", + "customResponseHeaders": "Intestazioni di risposta personalizzate", + "customResponseHeadersDescription": "Intestazioni di risposta restituite al client. Una per riga: Nome-Intestazione: valore", "headersValidationError": "Le intestazioni devono essere nel formato: Intestazione-Nome: valore.", "saveHealthCheck": "Salva Controllo Salute", "healthCheckSaved": "Controllo Salute Salvato", diff --git a/messages/ko-KR.json b/messages/ko-KR.json index 76facec3ed..1f45379a63 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -1964,10 +1964,10 @@ "retryAttempts": "재시도 횟수", "expectedResponseCodes": "예상 응답 코드", "expectedResponseCodesDescription": "정상 상태를 나타내는 HTTP 상태 코드입니다. 비워 두면 200-300이 정상으로 간주됩니다.", - "customRequestHeaders": "사용자 정의 헤더", - "customRequestHeadersDescription": "헤더는 새 줄로 구분됨: Header-Name: value", - "customResponseHeaders": "Custom Response Headers", - "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", + "customRequestHeaders": "사용자 정의 요청 헤더", + "customRequestHeadersDescription": "다운스트림 대상으로 전송되는 요청 헤더. 한 줄에 하나씩: 헤더-이름: 값", + "customResponseHeaders": "사용자 정의 응답 헤더", + "customResponseHeadersDescription": "클라이언트로 반환되는 응답 헤더. 한 줄에 하나씩: 헤더-이름: 값", "headersValidationError": "헤더는 형식이어야 합니다: 헤더명: 값.", "saveHealthCheck": "상태 확인 저장", "healthCheckSaved": "상태 확인이 저장되었습니다.", diff --git a/messages/nb-NO.json b/messages/nb-NO.json index 2aa54d52ab..0fd21f858d 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -1964,10 +1964,10 @@ "retryAttempts": "Forsøk på nytt", "expectedResponseCodes": "Forventede svarkoder", "expectedResponseCodesDescription": "HTTP-statuskode som indikerer sunn status. Hvis den blir stående tom, regnes 200-300 som sunn.", - "customRequestHeaders": "Egendefinerte topptekster", - "customRequestHeadersDescription": "Overskrifter som er adskilt med linje: Overskriftsnavn: verdi", - "customResponseHeaders": "Custom Response Headers", - "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", + "customRequestHeaders": "Egendefinerte forespørselshoder", + "customRequestHeadersDescription": "Forespørselshoder sendt til målene. Én per linje: Header-Navn: verdi", + "customResponseHeaders": "Egendefinerte svarhoder", + "customResponseHeadersDescription": "Svarhoder sendt tilbake til klienten. Én per linje: Header-Navn: verdi", "headersValidationError": "Topptekst må være i formatet: header-navn: verdi.", "saveHealthCheck": "Lagre Helsekontroll", "healthCheckSaved": "Helsekontroll Lagret", diff --git a/messages/nl-NL.json b/messages/nl-NL.json index 29215eb938..fc1e96abaf 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -1964,10 +1964,10 @@ "retryAttempts": "Herhaal Pogingen", "expectedResponseCodes": "Verwachte Reactiecodes", "expectedResponseCodesDescription": "HTTP-statuscode die gezonde status aangeeft. Indien leeg wordt 200-300 als gezond beschouwd.", - "customRequestHeaders": "Aangepaste headers", - "customRequestHeadersDescription": "Kopregeleinde: Header-Naam: waarde", - "customResponseHeaders": "Custom Response Headers", - "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", + "customRequestHeaders": "Aangepaste verzoekheaders", + "customRequestHeadersDescription": "Verzoekheaders die worden doorgestuurd naar het doel. Per regel één: Header-Naam: waarde", + "customResponseHeaders": "Aangepaste antwoordheaders", + "customResponseHeadersDescription": "Antwoordheaders die worden teruggestuurd naar de client. Per regel één: Header-Naam: waarde", "headersValidationError": "Headers moeten in het formaat zijn: Header-Naam: waarde.", "saveHealthCheck": "Opslaan Gezondheidscontrole", "healthCheckSaved": "Gezondheidscontrole Opgeslagen", diff --git a/messages/pl-PL.json b/messages/pl-PL.json index 205b34ded3..ebb8f0c28b 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -1964,10 +1964,10 @@ "retryAttempts": "Próby Ponowienia", "expectedResponseCodes": "Oczekiwane Kody Odpowiedzi", "expectedResponseCodesDescription": "Kod statusu HTTP, który wskazuje zdrowy status. Jeśli pozostanie pusty, uznaje się 200-300 za zdrowy.", - "customRequestHeaders": "Niestandardowe nagłówki", - "customRequestHeadersDescription": "Nagłówki oddzielone: Nazwa nagłówka: wartość", - "customResponseHeaders": "Custom Response Headers", - "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", + "customRequestHeaders": "Niestandardowe nagłówki żądania", + "customRequestHeadersDescription": "Nagłówki żądania wysyłane do celów. Jeden w wierszu: Nazwa-Nagłówka: wartość", + "customResponseHeaders": "Niestandardowe nagłówki odpowiedzi", + "customResponseHeadersDescription": "Nagłówki odpowiedzi zwracane do klienta. Jeden w wierszu: Nazwa-Nagłówka: wartość", "headersValidationError": "Nagłówki muszą być w formacie: Nazwa nagłówka: wartość.", "saveHealthCheck": "Zapisz Kontrolę Zdrowia", "healthCheckSaved": "Kontrola Zdrowia Zapisana", diff --git a/messages/pt-PT.json b/messages/pt-PT.json index b3086aa1c3..697280c644 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -1964,10 +1964,10 @@ "retryAttempts": "Tentativas de Repetição", "expectedResponseCodes": "Códigos de Resposta Esperados", "expectedResponseCodesDescription": "Código de status HTTP que indica estado saudável. Se deixado em branco, 200-300 é considerado saudável.", - "customRequestHeaders": "Cabeçalhos Personalizados", - "customRequestHeadersDescription": "Separados por cabeçalhos da nova linha: Nome do Cabeçalho: valor", - "customResponseHeaders": "Custom Response Headers", - "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", + "customRequestHeaders": "Cabeçalhos de Pedido Personalizados", + "customRequestHeadersDescription": "Cabeçalhos de pedido enviados para os destinos. Um por linha: Nome-Cabeçalho: valor", + "customResponseHeaders": "Cabeçalhos de Resposta Personalizados", + "customResponseHeadersDescription": "Cabeçalhos de resposta enviados de volta ao cliente. Um por linha: Nome-Cabeçalho: valor", "headersValidationError": "Cabeçalhos devem estar no formato: Nome do Cabeçalho: valor.", "saveHealthCheck": "Salvar Verificação de Saúde", "healthCheckSaved": "Verificação de Saúde Salva", diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 556d1d35c1..068762903f 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -1964,10 +1964,10 @@ "retryAttempts": "Количество попыток повторного запроса", "expectedResponseCodes": "Ожидаемые коды ответов", "expectedResponseCodesDescription": "HTTP-код состояния, указывающий на здоровое состояние. Если оставить пустым, 200-300 считается здоровым.", - "customRequestHeaders": "Пользовательские заголовки", - "customRequestHeadersDescription": "Заголовки новой строки, разделённые: название заголовка: значение", - "customResponseHeaders": "Custom Response Headers", - "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", + "customRequestHeaders": "Пользовательские заголовки запроса", + "customRequestHeadersDescription": "Заголовки запроса, отправляемые целевым серверам. По одному в строке: Имя-Заголовка: значение", + "customResponseHeaders": "Пользовательские заголовки ответа", + "customResponseHeadersDescription": "Заголовки ответа, возвращаемые клиенту. По одному в строке: Имя-Заголовка: значение", "headersValidationError": "Заголовки должны быть в формате: Название заголовка: значение.", "saveHealthCheck": "Сохранить проверку здоровья", "healthCheckSaved": "Проверка здоровья сохранена", diff --git a/messages/tr-TR.json b/messages/tr-TR.json index 3b5a324e2b..00cc9f4b8c 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -1964,10 +1964,10 @@ "retryAttempts": "Tekrar Deneme Girişimleri", "expectedResponseCodes": "Beklenen Yanıt Kodları", "expectedResponseCodesDescription": "Sağlıklı durumu gösteren HTTP durum kodu. Boş bırakılırsa, 200-300 arası sağlıklı kabul edilir.", - "customRequestHeaders": "Özel Başlıklar", - "customRequestHeadersDescription": "Başlıklar yeni satırla ayrılmış: Başlık-Adı: değer", - "customResponseHeaders": "Custom Response Headers", - "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", + "customRequestHeaders": "Özel İstek Başlıkları", + "customRequestHeadersDescription": "Hedeflere gönderilen istek başlıkları. Satır başına bir tane: Başlık-Adı: değer", + "customResponseHeaders": "Özel Yanıt Başlıkları", + "customResponseHeadersDescription": "İstemciye geri gönderilen yanıt başlıkları. Satır başına bir tane: Başlık-Adı: değer", "headersValidationError": "Başlıklar şu formatta olmalıdır: Başlık-Adı: değer.", "saveHealthCheck": "Sağlık Kontrolünü Kaydet", "healthCheckSaved": "Sağlık Kontrolü Kaydedildi", diff --git a/messages/zh-CN.json b/messages/zh-CN.json index dd01d78f84..95b881ac33 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -1964,10 +1964,10 @@ "retryAttempts": "重试次数", "expectedResponseCodes": "期望响应代码", "expectedResponseCodesDescription": "HTTP 状态码表示健康状态。如留空,200-300 被视为健康。", - "customRequestHeaders": "自定义标题", - "customRequestHeadersDescription": "头部新行分隔:头部名称:值", - "customResponseHeaders": "Custom Response Headers", - "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", + "customRequestHeaders": "自定义请求标头", + "customRequestHeadersDescription": "转发到目标的请求标头。每行一个:标头名称:值", + "customResponseHeaders": "自定义响应标头", + "customResponseHeadersDescription": "返回给客户端的响应标头。每行一个:标头名称:值", "headersValidationError": "头部必须是格式:头部名称:值。", "saveHealthCheck": "保存健康检查", "healthCheckSaved": "健康检查已保存", diff --git a/messages/zh-TW.json b/messages/zh-TW.json index e6c4a95f22..47d803b5df 100644 --- a/messages/zh-TW.json +++ b/messages/zh-TW.json @@ -1555,10 +1555,10 @@ "retryAttempts": "重試次數", "expectedResponseCodes": "期望響應代碼", "expectedResponseCodesDescription": "HTTP 狀態碼表示健康狀態。如留空,200-300 被視為健康。", - "customRequestHeaders": "自訂 Headers", - "customRequestHeadersDescription": "Header 斷行分隔:Header 名稱:值", - "customResponseHeaders": "Custom Response Headers", - "customResponseHeadersDescription": "Response headers sent back to the client. One per line: Header-Name: value", + "customRequestHeaders": "自訂請求 Headers", + "customRequestHeadersDescription": "轉發至目標的請求標頭。每行一個:Header-名稱:值", + "customResponseHeaders": "自訂回應 Headers", + "customResponseHeadersDescription": "回傳給客戶端的回應標頭。每行一個:Header-名稱:值", "headersValidationError": "Header 必須是格式:Header 名稱:值。", "saveHealthCheck": "保存健康檢查", "healthCheckSaved": "健康檢查已保存", From 6c7d345f033ca670f4d80806dfce573c4d5856dd Mon Sep 17 00:00:00 2001 From: Julian van der Horst Date: Wed, 27 May 2026 18:05:43 +0200 Subject: [PATCH 3/9] Reverted package-lock.json --- package-lock.json | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index d06ae5554c..8c241554ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1058,6 +1058,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -3033,6 +3034,7 @@ "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -6979,6 +6981,7 @@ "resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.1.6.tgz", "integrity": "sha512-TYqkioRS45wTR5il3dYk/SbUjjEdhSwh9BtRNB99qNH1pXAwA45H7rAuxehiu8iJQJH0IyIr+6n62gBz9ezmsw==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -8439,6 +8442,7 @@ "version": "5.90.21", "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz", "integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==", + "peer": true, "dependencies": { "@tanstack/query-core": "5.90.20" }, @@ -8554,6 +8558,7 @@ "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*" } @@ -8901,6 +8906,7 @@ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -8996,6 +9002,7 @@ "integrity": "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -9023,6 +9030,7 @@ "integrity": "sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*", "pg-protocol": "*", @@ -9048,6 +9056,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -9058,6 +9067,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -9144,8 +9154,7 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/@types/ws": { "version": "8.18.1", @@ -9219,6 +9228,7 @@ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -9692,6 +9702,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -10141,6 +10152,7 @@ "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/types": "^7.26.0" } @@ -10212,6 +10224,7 @@ "integrity": "sha512-Ba0KR+Fzxh2jDRhdg6TSH0SJGzb8C0aBY4hR8w8madIdIzzC6Y1+kx5qR6eS1Z+Gy20h6ZU28aeyg0z1VIrShQ==", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" @@ -10340,6 +10353,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -11246,6 +11260,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -11686,7 +11701,6 @@ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz", "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==", "license": "(MPL-2.0 OR Apache-2.0)", - "peer": true, "engines": { "node": ">=20" }, @@ -12321,6 +12335,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -12406,6 +12421,7 @@ "integrity": "sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -12542,6 +12558,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -12935,6 +12952,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -15352,7 +15370,6 @@ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", "license": "MIT", - "peer": true, "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" @@ -15363,7 +15380,6 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -15452,6 +15468,7 @@ "resolved": "https://registry.npmjs.org/next/-/next-15.5.15.tgz", "integrity": "sha512-VSqCrJwtLVGwAVE0Sb/yikrQfkwkZW9p+lL/J4+xe+G3ZA+QnWPqgcfH1tDUEuk9y+pthzzVFp4L/U8JerMfMQ==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "15.5.15", "@swc/helpers": "0.5.15", @@ -16411,6 +16428,7 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.12.0", "pg-pool": "^3.13.0", @@ -16918,6 +16936,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -16949,6 +16968,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -17241,6 +17261,7 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.2.tgz", "integrity": "sha512-1CHvcDYzuRUNOflt4MOq3ZM46AronNJtQ1S7tnX6YN4y72qhgiUItpacZUAQ0TyWYci3yz1X+rXaSxiuEm86PA==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -18702,7 +18723,8 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.3.2", @@ -19177,6 +19199,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -19604,6 +19627,7 @@ "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", "license": "MIT", + "peer": true, "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", @@ -19810,6 +19834,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } From 9d6062619c2dd1e2dc438a4377e40b91378b00fc Mon Sep 17 00:00:00 2001 From: Julian van der Horst Date: Wed, 27 May 2026 18:21:52 +0200 Subject: [PATCH 4/9] Forgot about blueprints --- server/lib/blueprints/proxyResources.ts | 18 ++++++++++++++---- server/lib/blueprints/types.ts | 4 +++- server/routers/resource/getResource.ts | 5 +++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/server/lib/blueprints/proxyResources.ts b/server/lib/blueprints/proxyResources.ts index 1789919629..6fac02122f 100644 --- a/server/lib/blueprints/proxyResources.ts +++ b/server/lib/blueprints/proxyResources.ts @@ -210,8 +210,16 @@ export async function updateProxyResources( ? true : resourceData.ssl; let headers = ""; - if (resourceData.headers) { - headers = JSON.stringify(resourceData.headers); + const requestHeadersData = [ + ...(resourceData.headers ?? []), + ...(resourceData.requestHeaders ?? []) + ]; + if (requestHeadersData.length > 0) { + headers = JSON.stringify(requestHeadersData); + } + let responseHeaders = ""; + if (resourceData.responseHeaders) { + responseHeaders = JSON.stringify(resourceData.responseHeaders); } if (existingResource) { @@ -265,7 +273,8 @@ export async function updateProxyResources( ] ? resourceData.auth["whitelist-users"].length > 0 : false, - headers: headers || null, + requestHeaders: headers || null, + responseHeaders: responseHeaders || null, applyRules: resourceData.rules && resourceData.rules.length > 0, maintenanceModeEnabled: @@ -724,7 +733,8 @@ export async function updateProxyResources( setHostHeader: resourceData["host-header"] || null, tlsServerName: resourceData["tls-server-name"] || null, ssl: resourceSsl, - headers: headers || null, + requestHeaders: headers || null, + responseHeaders: responseHeaders || null, applyRules: resourceData.rules && resourceData.rules.length > 0, maintenanceModeEnabled: resourceData.maintenance?.enabled, diff --git a/server/lib/blueprints/types.ts b/server/lib/blueprints/types.ts index 13f4caa8fb..2bff77d10e 100644 --- a/server/lib/blueprints/types.ts +++ b/server/lib/blueprints/types.ts @@ -175,7 +175,9 @@ export const ResourceSchema = z auth: AuthSchema.optional(), "host-header": z.string().optional(), "tls-server-name": z.string().optional(), - headers: z.array(HeaderSchema).optional(), + headers: z.array(HeaderSchema).optional(), // deprecated alias for requestHeaders + requestHeaders: z.array(HeaderSchema).optional(), + responseHeaders: z.array(HeaderSchema).optional(), rules: z.array(RuleSchema).optional(), maintenance: MaintenanceSchema.optional() }) diff --git a/server/routers/resource/getResource.ts b/server/routers/resource/getResource.ts index bbd58320c7..d63012f187 100644 --- a/server/routers/resource/getResource.ts +++ b/server/routers/resource/getResource.ts @@ -43,9 +43,10 @@ async function query(resourceId?: number, niceId?: string, orgId?: string) { export type GetResourceResponse = Omit< NonNullable>>, - "headers" + "requestHeaders" | "responseHeaders" > & { - headers: { name: string; value: string }[] | null; + requestHeaders: { name: string; value: string }[] | null; + responseHeaders: { name: string; value: string }[] | null; }; registry.registerPath({ From 382f5d52184dcb7b712d15427b8cd87a4b625eef Mon Sep 17 00:00:00 2001 From: Julian van der Horst Date: Wed, 27 May 2026 18:36:33 +0200 Subject: [PATCH 5/9] Fixed copilot issue. --- server/lib/blueprints/proxyResources.ts | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/server/lib/blueprints/proxyResources.ts b/server/lib/blueprints/proxyResources.ts index 6fac02122f..f0194c6500 100644 --- a/server/lib/blueprints/proxyResources.ts +++ b/server/lib/blueprints/proxyResources.ts @@ -209,18 +209,17 @@ export async function updateProxyResources( resourceData.ssl == undefined || resourceData.ssl == null ? true : resourceData.ssl; - let headers = ""; const requestHeadersData = [ ...(resourceData.headers ?? []), ...(resourceData.requestHeaders ?? []) ]; - if (requestHeadersData.length > 0) { - headers = JSON.stringify(requestHeadersData); - } - let responseHeaders = ""; - if (resourceData.responseHeaders) { - responseHeaders = JSON.stringify(resourceData.responseHeaders); - } + const requestHeadersJson = + requestHeadersData.length > 0 + ? JSON.stringify(requestHeadersData) + : null; + const responseHeadersJson = resourceData.responseHeaders + ? JSON.stringify(resourceData.responseHeaders) + : null; if (existingResource) { let domain; @@ -273,8 +272,8 @@ export async function updateProxyResources( ] ? resourceData.auth["whitelist-users"].length > 0 : false, - requestHeaders: headers || null, - responseHeaders: responseHeaders || null, + requestHeaders: requestHeadersJson, + responseHeaders: responseHeadersJson, applyRules: resourceData.rules && resourceData.rules.length > 0, maintenanceModeEnabled: @@ -733,8 +732,8 @@ export async function updateProxyResources( setHostHeader: resourceData["host-header"] || null, tlsServerName: resourceData["tls-server-name"] || null, ssl: resourceSsl, - requestHeaders: headers || null, - responseHeaders: responseHeaders || null, + requestHeaders: requestHeadersJson, + responseHeaders: responseHeadersJson, applyRules: resourceData.rules && resourceData.rules.length > 0, maintenanceModeEnabled: resourceData.maintenance?.enabled, From 8dee505eb25ec517f4515df4d5f2da7d590bfbf4 Mon Sep 17 00:00:00 2001 From: Julian van der Horst Date: Wed, 27 May 2026 18:38:56 +0200 Subject: [PATCH 6/9] Fixed copilot issue. --- server/setup/scriptsSqlite/1.18.5.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server/setup/scriptsSqlite/1.18.5.ts b/server/setup/scriptsSqlite/1.18.5.ts index 2edd523f09..d72d801a8f 100644 --- a/server/setup/scriptsSqlite/1.18.5.ts +++ b/server/setup/scriptsSqlite/1.18.5.ts @@ -15,19 +15,20 @@ export default async function migration() { db.transaction(() => { db.prepare( - `ALTER TABLE 'resources' RENAME COLUMN 'headers' TO 'requestHeaders';` + `ALTER TABLE "resources" RENAME COLUMN "headers" TO "requestHeaders";` ).run(); db.prepare( - `ALTER TABLE 'resources' ADD 'responseHeaders' text;` + `ALTER TABLE "resources" ADD "responseHeaders" text;` ).run(); })(); - db.pragma("foreign_keys = ON"); - console.log("Migrated database"); } catch (e) { console.log("Failed to migrate db:", e); throw e; + } finally { + db.pragma("foreign_keys = ON"); + db.close(); } console.log(`${version} migration complete`); From 6952d3bee7cde9409d5a51fd68960bc17cca5a5d Mon Sep 17 00:00:00 2001 From: Julian van der Horst Date: Wed, 27 May 2026 18:42:00 +0200 Subject: [PATCH 7/9] Fixed copilot issue. --- server/routers/resource/updateResource.ts | 28 +++++++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index fc8416282d..97513dcb12 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -24,7 +24,10 @@ import { import { registry } from "@server/openApi"; import { OpenAPITags } from "@server/openApi"; import { createCertificate } from "#dynamic/routers/certificates/createCertificate"; -import { validateAndConstructDomain, checkWildcardDomainConflict } from "@server/lib/domainUtils"; +import { + validateAndConstructDomain, + checkWildcardDomainConflict +} from "@server/lib/domainUtils"; import { build } from "@server/build"; import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; @@ -58,6 +61,10 @@ const updateHttpResourceBodySchema = z tlsServerName: z.string().nullable().optional(), setHostHeader: z.string().nullable().optional(), skipToIdpId: z.int().positive().nullable().optional(), + headers: z + .array(z.strictObject({ name: z.string(), value: z.string() })) + .nullable() + .optional(), // deprecated alias for requestHeaders requestHeaders: z .array(z.strictObject({ name: z.string(), value: z.string() })) .nullable() @@ -117,6 +124,7 @@ const updateHttpResourceBodySchema = z (data) => { const validHeaderName = /^[a-zA-Z0-9!#$%&'*+\-.^_`|~]+$/; const allHeaders = [ + ...(data.headers ?? []), ...(data.requestHeaders ?? []), ...(data.responseHeaders ?? []) ]; @@ -130,6 +138,7 @@ const updateHttpResourceBodySchema = z (data) => { const validHeaderValue = /^[\t\x20-\x7E]*$/; const allHeaders = [ + ...(data.headers ?? []), ...(data.requestHeaders ?? []), ...(data.responseHeaders ?? []) ]; @@ -143,6 +152,7 @@ const updateHttpResourceBodySchema = z (data) => { const templatePattern = /\{\{[^}]+\}\}/; const allHeaders = [ + ...(data.headers ?? []), ...(data.requestHeaders ?? []), ...(data.responseHeaders ?? []) ]; @@ -470,10 +480,18 @@ async function updateHttpResource( } let requestHeaders = undefined; - if (updateData.requestHeaders) { - requestHeaders = JSON.stringify(updateData.requestHeaders); - } else if (updateData.requestHeaders === null) { - requestHeaders = null; + const mergedRequestHeaders = [ + ...(updateData.headers ?? []), + ...(updateData.requestHeaders ?? []) + ]; + if ( + updateData.headers !== undefined || + updateData.requestHeaders !== undefined + ) { + requestHeaders = + mergedRequestHeaders.length > 0 + ? JSON.stringify(mergedRequestHeaders) + : null; } let responseHeaders = undefined; From 713e24503db0562f81eada4e0062466d061a440e Mon Sep 17 00:00:00 2001 From: Julian van der Horst Date: Wed, 27 May 2026 20:33:10 +0200 Subject: [PATCH 8/9] Removed migrations --- server/setup/migrationsPg.ts | 4 +--- server/setup/migrationsSqlite.ts | 4 +--- server/setup/scriptsPg/1.18.5.ts | 30 ------------------------ server/setup/scriptsSqlite/1.18.5.ts | 35 ---------------------------- 4 files changed, 2 insertions(+), 71 deletions(-) delete mode 100644 server/setup/scriptsPg/1.18.5.ts delete mode 100644 server/setup/scriptsSqlite/1.18.5.ts diff --git a/server/setup/migrationsPg.ts b/server/setup/migrationsPg.ts index 1a7d530c47..0b8af06bca 100644 --- a/server/setup/migrationsPg.ts +++ b/server/setup/migrationsPg.ts @@ -25,7 +25,6 @@ import m16 from "./scriptsPg/1.17.0"; import m17 from "./scriptsPg/1.18.0"; import m18 from "./scriptsPg/1.18.3"; import m19 from "./scriptsPg/1.18.4"; -import m20 from "./scriptsPg/1.18.5"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -50,8 +49,7 @@ const migrations = [ { version: "1.17.0", run: m16 }, { version: "1.18.0", run: m17 }, { version: "1.18.3", run: m18 }, - { version: "1.18.4", run: m19 }, - { version: "1.18.5", run: m20 } + { version: "1.18.4", run: m19 } // Add new migrations here as they are created ] as { version: string; diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index 75f414c75d..837b039f7d 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -43,7 +43,6 @@ import m37 from "./scriptsSqlite/1.17.0"; import m38 from "./scriptsSqlite/1.18.0"; import m39 from "./scriptsSqlite/1.18.3"; import m40 from "./scriptsSqlite/1.18.4"; -import m41 from "./scriptsSqlite/1.18.5"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -84,8 +83,7 @@ const migrations = [ { version: "1.17.0", run: m37 }, { version: "1.18.0", run: m38 }, { version: "1.18.3", run: m39 }, - { version: "1.18.4", run: m40 }, - { version: "1.18.5", run: m41 } + { version: "1.18.4", run: m40 } // Add new migrations here as they are created ] as const; diff --git a/server/setup/scriptsPg/1.18.5.ts b/server/setup/scriptsPg/1.18.5.ts deleted file mode 100644 index 5bceb41cf2..0000000000 --- a/server/setup/scriptsPg/1.18.5.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { db } from "@server/db/pg/driver"; -import { sql } from "drizzle-orm"; - -const version = "1.18.5"; - -export default async function migration() { - console.log(`Running setup script ${version}...`); - - try { - await db.execute(sql`BEGIN`); - - await db.execute(sql` - ALTER TABLE "resources" RENAME COLUMN "headers" TO "requestHeaders"; - `); - - await db.execute(sql` - ALTER TABLE "resources" ADD COLUMN "responseHeaders" text; - `); - - await db.execute(sql`COMMIT`); - console.log("Migrated database"); - } catch (e) { - await db.execute(sql`ROLLBACK`); - console.log("Unable to migrate database"); - console.log(e); - throw e; - } - - console.log(`${version} migration complete`); -} diff --git a/server/setup/scriptsSqlite/1.18.5.ts b/server/setup/scriptsSqlite/1.18.5.ts deleted file mode 100644 index d72d801a8f..0000000000 --- a/server/setup/scriptsSqlite/1.18.5.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { APP_PATH } from "@server/lib/consts"; -import Database from "better-sqlite3"; -import path from "path"; - -const version = "1.18.5"; - -export default async function migration() { - console.log(`Running setup script ${version}...`); - - const location = path.join(APP_PATH, "db", "db.sqlite"); - const db = new Database(location); - - try { - db.pragma("foreign_keys = OFF"); - - db.transaction(() => { - db.prepare( - `ALTER TABLE "resources" RENAME COLUMN "headers" TO "requestHeaders";` - ).run(); - db.prepare( - `ALTER TABLE "resources" ADD "responseHeaders" text;` - ).run(); - })(); - - console.log("Migrated database"); - } catch (e) { - console.log("Failed to migrate db:", e); - throw e; - } finally { - db.pragma("foreign_keys = ON"); - db.close(); - } - - console.log(`${version} migration complete`); -} From a8c1ddb4481d39c37700d7b5f968fe80b506cab4 Mon Sep 17 00:00:00 2001 From: Julian van der Horst Date: Tue, 7 Jul 2026 11:57:43 +0200 Subject: [PATCH 9/9] Merge should be complete --- server/lib/blueprints/proxyResources.ts | 0 server/lib/blueprints/publicResources.ts | 25 +++++++++++++------ server/routers/resource/updateResource.ts | 6 ++++- .../resources/proxy/[niceId]/proxy/page.tsx | 0 4 files changed, 23 insertions(+), 8 deletions(-) delete mode 100644 server/lib/blueprints/proxyResources.ts delete mode 100644 src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx diff --git a/server/lib/blueprints/proxyResources.ts b/server/lib/blueprints/proxyResources.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/server/lib/blueprints/publicResources.ts b/server/lib/blueprints/publicResources.ts index 92d6239fa2..03e3a724a9 100644 --- a/server/lib/blueprints/publicResources.ts +++ b/server/lib/blueprints/publicResources.ts @@ -237,10 +237,18 @@ export async function updatePublicResources( resourceData.ssl == undefined || resourceData.ssl == null ? true : resourceData.ssl; - let headers = ""; - if (resourceData.headers) { - headers = JSON.stringify(resourceData.headers); - } + // `headers` is a deprecated alias for `requestHeaders` + const mergedRequestHeaders = [ + ...(resourceData.headers ?? []), + ...(resourceData.requestHeaders ?? []) + ]; + const requestHeaders = + mergedRequestHeaders.length > 0 + ? JSON.stringify(mergedRequestHeaders) + : null; + const responseHeaders = resourceData.responseHeaders?.length + ? JSON.stringify(resourceData.responseHeaders) + : null; if (["ssh", "rdp", "vnc"].includes(resourceData.mode || "")) { const isLicensed = await isLicensedOrSubscribed( @@ -380,7 +388,8 @@ export async function updatePublicResources( ? resourceData.auth["whitelist-users"].length > 0 : false, - headers: headers || null, + requestHeaders, + responseHeaders, applyRules: resourceData.rules && resourceData.rules.length > 0, @@ -566,7 +575,8 @@ export async function updatePublicResources( setHostHeader: resourceData["host-header"] || null, tlsServerName: resourceData["tls-server-name"] || null, - headers: headers || null, + requestHeaders, + responseHeaders, maintenanceModeEnabled: resourceData.maintenance?.enabled, maintenanceModeType: resourceData.maintenance?.type, @@ -1152,7 +1162,8 @@ export async function updatePublicResources( setHostHeader: resourceData["host-header"] || null, tlsServerName: resourceData["tls-server-name"] || null, ssl: resourceSsl, - headers: headers || null, + requestHeaders, + responseHeaders, applyRules: resourceData.rules && resourceData.rules.length > 0, pamMode: resourceData["auth-daemon"]?.pam || "passthrough", diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index 532ba2b25b..fd2e8c37ef 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -663,6 +663,10 @@ async function updateHttpResource( responseHeaders = null; } + updateData.headers = undefined; + updateData.requestHeaders = undefined; + updateData.responseHeaders = undefined; + if (!isLicensed) { updateData.maintenanceModeEnabled = undefined; updateData.maintenanceModeType = undefined; @@ -715,7 +719,7 @@ async function updateHttpResource( const updatedResource = await db .update(resources) - .set({ ...resourceOnlyData, headers }) + .set({ ...resourceOnlyData, requestHeaders, responseHeaders }) .where(eq(resources.resourceId, resource.resourceId)) .returning(); diff --git a/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx b/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx deleted file mode 100644 index e69de29bb2..0000000000