mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-23 18:58:51 +02:00
Merge pull request #3172 from Gulianrdgd/feature-response-headers
Feature response headers
This commit is contained in:
+4
-2
@@ -2826,8 +2826,10 @@
|
|||||||
"retryAttempts": "Опити за повторно",
|
"retryAttempts": "Опити за повторно",
|
||||||
"expectedResponseCodes": "Очаквани кодове за отговор",
|
"expectedResponseCodes": "Очаквани кодове за отговор",
|
||||||
"expectedResponseCodesDescription": "HTTP статус код, указващ здравословно състояние. Ако бъде оставено празно, между 200-300 се счита за здравословно.",
|
"expectedResponseCodesDescription": "HTTP статус код, указващ здравословно състояние. Ако бъде оставено празно, между 200-300 се счита за здравословно.",
|
||||||
"customHeaders": "Персонализирани заглавия",
|
"customRequestHeaders": "Персонализирани заглавия на заявката",
|
||||||
"customHeadersDescription": "Add custom headers to be sent when proxying requests. One per line in the format Header-Name: value",
|
"customRequestHeadersDescription": "Заглавия на заявката, изпратени до целевите сървъри. По едно на ред: Заглавие-Име: стойност",
|
||||||
|
"customResponseHeaders": "Персонализирани заглавия на отговора",
|
||||||
|
"customResponseHeadersDescription": "Заглавия на отговора, върнати на клиента. По едно на ред: Заглавие-Име: стойност",
|
||||||
"headersValidationError": "Заглавията трябва да бъдат във формат: Име на заглавието: стойност.",
|
"headersValidationError": "Заглавията трябва да бъдат във формат: Име на заглавието: стойност.",
|
||||||
"saveHealthCheck": "Запазване на проверка на здравето",
|
"saveHealthCheck": "Запазване на проверка на здравето",
|
||||||
"healthCheckSaved": "Проверка на здравето е запазена",
|
"healthCheckSaved": "Проверка на здравето е запазена",
|
||||||
|
|||||||
+4
-2
@@ -2826,8 +2826,10 @@
|
|||||||
"retryAttempts": "Opakovat pokusy",
|
"retryAttempts": "Opakovat pokusy",
|
||||||
"expectedResponseCodes": "Očekávané kódy odezvy",
|
"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é.",
|
"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í",
|
"customRequestHeaders": "Vlastní záhlaví požadavku",
|
||||||
"customHeadersDescription": "Záhlaví oddělená nová řádka: hodnota",
|
"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.",
|
"headersValidationError": "Headers must be in the format: Header-Name: value.",
|
||||||
"saveHealthCheck": "Uložit kontrolu stavu",
|
"saveHealthCheck": "Uložit kontrolu stavu",
|
||||||
"healthCheckSaved": "Kontrola stavu uložena",
|
"healthCheckSaved": "Kontrola stavu uložena",
|
||||||
|
|||||||
+4
-2
@@ -2826,8 +2826,10 @@
|
|||||||
"retryAttempts": "Forsøg igen",
|
"retryAttempts": "Forsøg igen",
|
||||||
"expectedResponseCodes": "Forventede svarkoder",
|
"expectedResponseCodes": "Forventede svarkoder",
|
||||||
"expectedResponseCodesDescription": "HTTP-statuskode som indikerer sund status. Hvis den bliver stående tom, regnes 200-300 som sund.",
|
"expectedResponseCodesDescription": "HTTP-statuskode som indikerer sund status. Hvis den bliver stående tom, regnes 200-300 som sund.",
|
||||||
"customHeaders": "Brugerdefinerede headers",
|
"customRequestHeaders": "Brugerdefinerede anmodnings-headers",
|
||||||
"customHeadersDescription": "Headers som er adskilt med linje: Overskriftsnavn: værdi",
|
"customRequestHeadersDescription": "Anmodnings-headers som sendes til destinationerne. Én pr. linje: Header-navn: værdi",
|
||||||
|
"customResponseHeaders": "Brugerdefinerede svar-headers",
|
||||||
|
"customResponseHeadersDescription": "Svar-headers som sendes tilbage til klienten. Én pr. linje: Header-navn: værdi",
|
||||||
"headersValidationError": "Header skal være i formatet: header-navn: værdi.",
|
"headersValidationError": "Header skal være i formatet: header-navn: værdi.",
|
||||||
"saveHealthCheck": "Gem Sundhedstjek",
|
"saveHealthCheck": "Gem Sundhedstjek",
|
||||||
"healthCheckSaved": "Sundhedstjek Gemt",
|
"healthCheckSaved": "Sundhedstjek Gemt",
|
||||||
|
|||||||
+4
-2
@@ -2826,8 +2826,10 @@
|
|||||||
"retryAttempts": "Wiederholungsversuche",
|
"retryAttempts": "Wiederholungsversuche",
|
||||||
"expectedResponseCodes": "Erwartete Antwortcodes",
|
"expectedResponseCodes": "Erwartete Antwortcodes",
|
||||||
"expectedResponseCodesDescription": "HTTP-Statuscode, der einen gesunden Zustand anzeigt. Wenn leer gelassen, wird 200-300 als gesund angesehen.",
|
"expectedResponseCodesDescription": "HTTP-Statuscode, der einen gesunden Zustand anzeigt. Wenn leer gelassen, wird 200-300 als gesund angesehen.",
|
||||||
"customHeaders": "Eigene Kopfzeilen",
|
"customRequestHeaders": "Eigene Anfrage-Header",
|
||||||
"customHeadersDescription": "Header neue Zeile getrennt: Header-Name: Wert",
|
"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.",
|
"headersValidationError": "Header müssen im Format Header-Name: Wert sein.",
|
||||||
"saveHealthCheck": "Gesundheits-Check speichern",
|
"saveHealthCheck": "Gesundheits-Check speichern",
|
||||||
"healthCheckSaved": "Gesundheits-Check gespeichert",
|
"healthCheckSaved": "Gesundheits-Check gespeichert",
|
||||||
|
|||||||
+4
-2
@@ -2829,8 +2829,10 @@
|
|||||||
"retryAttempts": "Retry Attempts",
|
"retryAttempts": "Retry Attempts",
|
||||||
"expectedResponseCodes": "Expected Response Codes",
|
"expectedResponseCodes": "Expected Response Codes",
|
||||||
"expectedResponseCodesDescription": "HTTP status code that indicates healthy status. If left blank, 200-300 is considered healthy.",
|
"expectedResponseCodesDescription": "HTTP status code that indicates healthy status. If left blank, 200-300 is considered healthy.",
|
||||||
"customHeaders": "Custom Request Headers",
|
"customRequestHeaders": "Custom Request Headers",
|
||||||
"customHeadersDescription": "Request headers sent to the downstream targets. Headers new line separated: Header-Name: value",
|
"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",
|
"headersValidationError": "Headers must be in the format: Header-Name: value",
|
||||||
"saveHealthCheck": "Save Health Check",
|
"saveHealthCheck": "Save Health Check",
|
||||||
"healthCheckSaved": "Health Check Saved",
|
"healthCheckSaved": "Health Check Saved",
|
||||||
|
|||||||
+4
-2
@@ -2826,8 +2826,10 @@
|
|||||||
"retryAttempts": "Intentos de Reintento",
|
"retryAttempts": "Intentos de Reintento",
|
||||||
"expectedResponseCodes": "Códigos de respuesta esperados",
|
"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.",
|
"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",
|
"customRequestHeaders": "Cabeceras de solicitud personalizadas",
|
||||||
"customHeadersDescription": "Nueva línea de cabeceras separada: Nombre de cabecera: valor",
|
"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.",
|
"headersValidationError": "Los encabezados deben estar en el formato: Nombre de cabecera: valor.",
|
||||||
"saveHealthCheck": "Guardar Chequeo de Salud",
|
"saveHealthCheck": "Guardar Chequeo de Salud",
|
||||||
"healthCheckSaved": "Chequeo de Salud Guardado",
|
"healthCheckSaved": "Chequeo de Salud Guardado",
|
||||||
|
|||||||
+4
-2
@@ -2826,8 +2826,10 @@
|
|||||||
"retryAttempts": "Tentatives de réessai",
|
"retryAttempts": "Tentatives de réessai",
|
||||||
"expectedResponseCodes": "Codes de réponse attendus",
|
"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.",
|
"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",
|
"customRequestHeaders": "En-têtes de requête personnalisés",
|
||||||
"customHeadersDescription": "En-têtes séparés par une nouvelle ligne: En-nom: valeur",
|
"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.",
|
"headersValidationError": "Les entêtes doivent être au format : Header-Name: valeur.",
|
||||||
"saveHealthCheck": "Sauvegarder la vérification de l'état de santé",
|
"saveHealthCheck": "Sauvegarder la vérification de l'état de santé",
|
||||||
"healthCheckSaved": "Vérification de l'état de santé enregistrée",
|
"healthCheckSaved": "Vérification de l'état de santé enregistrée",
|
||||||
|
|||||||
+4
-2
@@ -2826,8 +2826,10 @@
|
|||||||
"retryAttempts": "Tentativi di Riprova",
|
"retryAttempts": "Tentativi di Riprova",
|
||||||
"expectedResponseCodes": "Codici di Risposta Attesi",
|
"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.",
|
"expectedResponseCodesDescription": "Codice di stato HTTP che indica lo stato di salute. Se lasciato vuoto, considerato sano è compreso tra 200-300.",
|
||||||
"customHeaders": "Intestazioni Personalizzate",
|
"customRequestHeaders": "Intestazioni di richiesta personalizzate",
|
||||||
"customHeadersDescription": "Intestazioni nuova riga separate: Intestazione-Nome: valore",
|
"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.",
|
"headersValidationError": "Le intestazioni devono essere nel formato: Intestazione-Nome: valore.",
|
||||||
"saveHealthCheck": "Salva Controllo Salute",
|
"saveHealthCheck": "Salva Controllo Salute",
|
||||||
"healthCheckSaved": "Controllo Salute Salvato",
|
"healthCheckSaved": "Controllo Salute Salvato",
|
||||||
|
|||||||
+4
-2
@@ -2826,8 +2826,10 @@
|
|||||||
"retryAttempts": "재시도 횟수",
|
"retryAttempts": "재시도 횟수",
|
||||||
"expectedResponseCodes": "예상 응답 코드",
|
"expectedResponseCodes": "예상 응답 코드",
|
||||||
"expectedResponseCodesDescription": "정상 상태를 나타내는 HTTP 상태 코드입니다. 비워 두면 200-300이 정상으로 간주됩니다.",
|
"expectedResponseCodesDescription": "정상 상태를 나타내는 HTTP 상태 코드입니다. 비워 두면 200-300이 정상으로 간주됩니다.",
|
||||||
"customHeaders": "사용자 정의 헤더",
|
"customRequestHeaders": "사용자 정의 요청 헤더",
|
||||||
"customHeadersDescription": "헤더는 새 줄로 구분됨: Header-Name: value",
|
"customRequestHeadersDescription": "다운스트림 대상으로 전송되는 요청 헤더. 한 줄에 하나씩: 헤더-이름: 값",
|
||||||
|
"customResponseHeaders": "사용자 정의 응답 헤더",
|
||||||
|
"customResponseHeadersDescription": "클라이언트로 반환되는 응답 헤더. 한 줄에 하나씩: 헤더-이름: 값",
|
||||||
"headersValidationError": "헤더는 형식이어야 합니다: 헤더명: 값.",
|
"headersValidationError": "헤더는 형식이어야 합니다: 헤더명: 값.",
|
||||||
"saveHealthCheck": "상태 확인 저장",
|
"saveHealthCheck": "상태 확인 저장",
|
||||||
"healthCheckSaved": "상태 확인이 저장되었습니다.",
|
"healthCheckSaved": "상태 확인이 저장되었습니다.",
|
||||||
|
|||||||
+4
-2
@@ -2826,8 +2826,10 @@
|
|||||||
"retryAttempts": "Forsøk på nytt",
|
"retryAttempts": "Forsøk på nytt",
|
||||||
"expectedResponseCodes": "Forventede svarkoder",
|
"expectedResponseCodes": "Forventede svarkoder",
|
||||||
"expectedResponseCodesDescription": "HTTP-statuskode som indikerer sunn status. Hvis den blir stående tom, regnes 200-300 som sunn.",
|
"expectedResponseCodesDescription": "HTTP-statuskode som indikerer sunn status. Hvis den blir stående tom, regnes 200-300 som sunn.",
|
||||||
"customHeaders": "Egendefinerte topptekster",
|
"customRequestHeaders": "Egendefinerte forespørselshoder",
|
||||||
"customHeadersDescription": "Overskrifter som er adskilt med linje: Overskriftsnavn: verdi",
|
"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.",
|
"headersValidationError": "Topptekst må være i formatet: header-navn: verdi.",
|
||||||
"saveHealthCheck": "Lagre Helsekontroll",
|
"saveHealthCheck": "Lagre Helsekontroll",
|
||||||
"healthCheckSaved": "Helsekontroll Lagret",
|
"healthCheckSaved": "Helsekontroll Lagret",
|
||||||
|
|||||||
+4
-2
@@ -2826,8 +2826,10 @@
|
|||||||
"retryAttempts": "Herhaal Pogingen",
|
"retryAttempts": "Herhaal Pogingen",
|
||||||
"expectedResponseCodes": "Verwachte Reactiecodes",
|
"expectedResponseCodes": "Verwachte Reactiecodes",
|
||||||
"expectedResponseCodesDescription": "HTTP-statuscode die gezonde status aangeeft. Indien leeg wordt 200-300 als gezond beschouwd.",
|
"expectedResponseCodesDescription": "HTTP-statuscode die gezonde status aangeeft. Indien leeg wordt 200-300 als gezond beschouwd.",
|
||||||
"customHeaders": "Aangepaste headers",
|
"customRequestHeaders": "Aangepaste verzoekheaders",
|
||||||
"customHeadersDescription": "Kopregeleinde: Header-Naam: waarde",
|
"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.",
|
"headersValidationError": "Headers moeten in het formaat zijn: Header-Naam: waarde.",
|
||||||
"saveHealthCheck": "Opslaan Gezondheidscontrole",
|
"saveHealthCheck": "Opslaan Gezondheidscontrole",
|
||||||
"healthCheckSaved": "Gezondheidscontrole Opgeslagen",
|
"healthCheckSaved": "Gezondheidscontrole Opgeslagen",
|
||||||
|
|||||||
+4
-2
@@ -2826,8 +2826,10 @@
|
|||||||
"retryAttempts": "Próby Ponowienia",
|
"retryAttempts": "Próby Ponowienia",
|
||||||
"expectedResponseCodes": "Oczekiwane Kody Odpowiedzi",
|
"expectedResponseCodes": "Oczekiwane Kody Odpowiedzi",
|
||||||
"expectedResponseCodesDescription": "Kod statusu HTTP, który wskazuje zdrowy status. Jeśli pozostanie pusty, uznaje się 200-300 za zdrowy.",
|
"expectedResponseCodesDescription": "Kod statusu HTTP, który wskazuje zdrowy status. Jeśli pozostanie pusty, uznaje się 200-300 za zdrowy.",
|
||||||
"customHeaders": "Niestandardowe nagłówki",
|
"customRequestHeaders": "Niestandardowe nagłówki żądania",
|
||||||
"customHeadersDescription": "Nagłówki oddzielone: Nazwa nagłówka: wartość",
|
"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ść.",
|
"headersValidationError": "Nagłówki muszą być w formacie: Nazwa nagłówka: wartość.",
|
||||||
"saveHealthCheck": "Zapisz Kontrolę Zdrowia",
|
"saveHealthCheck": "Zapisz Kontrolę Zdrowia",
|
||||||
"healthCheckSaved": "Kontrola Zdrowia Zapisana",
|
"healthCheckSaved": "Kontrola Zdrowia Zapisana",
|
||||||
|
|||||||
+4
-2
@@ -2826,8 +2826,10 @@
|
|||||||
"retryAttempts": "Tentativas de Repetição",
|
"retryAttempts": "Tentativas de Repetição",
|
||||||
"expectedResponseCodes": "Códigos de Resposta Esperados",
|
"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.",
|
"expectedResponseCodesDescription": "Código de status HTTP que indica estado saudável. Se deixado em branco, 200-300 é considerado saudável.",
|
||||||
"customHeaders": "Cabeçalhos Personalizados",
|
"customRequestHeaders": "Cabeçalhos de Pedido Personalizados",
|
||||||
"customHeadersDescription": "Separados por cabeçalhos da nova linha: Nome do Cabeçalho: valor",
|
"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.",
|
"headersValidationError": "Cabeçalhos devem estar no formato: Nome do Cabeçalho: valor.",
|
||||||
"saveHealthCheck": "Salvar Verificação de Saúde",
|
"saveHealthCheck": "Salvar Verificação de Saúde",
|
||||||
"healthCheckSaved": "Verificação de Saúde Salva",
|
"healthCheckSaved": "Verificação de Saúde Salva",
|
||||||
|
|||||||
+4
-2
@@ -2826,8 +2826,10 @@
|
|||||||
"retryAttempts": "Количество попыток повторного запроса",
|
"retryAttempts": "Количество попыток повторного запроса",
|
||||||
"expectedResponseCodes": "Ожидаемые коды ответов",
|
"expectedResponseCodes": "Ожидаемые коды ответов",
|
||||||
"expectedResponseCodesDescription": "HTTP-код состояния, указывающий на здоровое состояние. Если оставить пустым, 200-300 считается здоровым.",
|
"expectedResponseCodesDescription": "HTTP-код состояния, указывающий на здоровое состояние. Если оставить пустым, 200-300 считается здоровым.",
|
||||||
"customHeaders": "Пользовательские заголовки",
|
"customRequestHeaders": "Пользовательские заголовки запроса",
|
||||||
"customHeadersDescription": "Заголовки новой строки, разделённые: название заголовка: значение",
|
"customRequestHeadersDescription": "Заголовки запроса, отправляемые целевым серверам. По одному в строке: Имя-Заголовка: значение",
|
||||||
|
"customResponseHeaders": "Пользовательские заголовки ответа",
|
||||||
|
"customResponseHeadersDescription": "Заголовки ответа, возвращаемые клиенту. По одному в строке: Имя-Заголовка: значение",
|
||||||
"headersValidationError": "Заголовки должны быть в формате: Название заголовка: значение.",
|
"headersValidationError": "Заголовки должны быть в формате: Название заголовка: значение.",
|
||||||
"saveHealthCheck": "Сохранить проверку здоровья",
|
"saveHealthCheck": "Сохранить проверку здоровья",
|
||||||
"healthCheckSaved": "Проверка здоровья сохранена",
|
"healthCheckSaved": "Проверка здоровья сохранена",
|
||||||
|
|||||||
+4
-2
@@ -2826,8 +2826,10 @@
|
|||||||
"retryAttempts": "Tekrar Deneme Girişimleri",
|
"retryAttempts": "Tekrar Deneme Girişimleri",
|
||||||
"expectedResponseCodes": "Beklenen Yanıt Kodları",
|
"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.",
|
"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",
|
"customRequestHeaders": "Özel İstek Başlıkları",
|
||||||
"customHeadersDescription": "Başlıklar yeni satırla ayrılmış: Başlık-Adı: değer",
|
"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.",
|
"headersValidationError": "Başlıklar şu formatta olmalıdır: Başlık-Adı: değer.",
|
||||||
"saveHealthCheck": "Sağlık Kontrolünü Kaydet",
|
"saveHealthCheck": "Sağlık Kontrolünü Kaydet",
|
||||||
"healthCheckSaved": "Sağlık Kontrolü Kaydedildi",
|
"healthCheckSaved": "Sağlık Kontrolü Kaydedildi",
|
||||||
|
|||||||
+4
-2
@@ -2826,8 +2826,10 @@
|
|||||||
"retryAttempts": "重试次数",
|
"retryAttempts": "重试次数",
|
||||||
"expectedResponseCodes": "期望响应代码",
|
"expectedResponseCodes": "期望响应代码",
|
||||||
"expectedResponseCodesDescription": "HTTP 状态码表示健康状态。如留空,200-300 被视为健康。",
|
"expectedResponseCodesDescription": "HTTP 状态码表示健康状态。如留空,200-300 被视为健康。",
|
||||||
"customHeaders": "自定义标题",
|
"customRequestHeaders": "自定义请求标头",
|
||||||
"customHeadersDescription": "头部新行分隔:头部名称:值",
|
"customRequestHeadersDescription": "转发到目标的请求标头。每行一个:标头名称:值",
|
||||||
|
"customResponseHeaders": "自定义响应标头",
|
||||||
|
"customResponseHeadersDescription": "返回给客户端的响应标头。每行一个:标头名称:值",
|
||||||
"headersValidationError": "头部必须是格式:头部名称:值。",
|
"headersValidationError": "头部必须是格式:头部名称:值。",
|
||||||
"saveHealthCheck": "保存健康检查",
|
"saveHealthCheck": "保存健康检查",
|
||||||
"healthCheckSaved": "健康检查已保存",
|
"healthCheckSaved": "健康检查已保存",
|
||||||
|
|||||||
+4
-2
@@ -1551,8 +1551,10 @@
|
|||||||
"retryAttempts": "重試次數",
|
"retryAttempts": "重試次數",
|
||||||
"expectedResponseCodes": "期望響應代碼",
|
"expectedResponseCodes": "期望響應代碼",
|
||||||
"expectedResponseCodesDescription": "HTTP 狀態碼表示健康狀態。如留空,200-300 被視為健康。",
|
"expectedResponseCodesDescription": "HTTP 狀態碼表示健康狀態。如留空,200-300 被視為健康。",
|
||||||
"customHeaders": "自訂 Headers",
|
"customRequestHeaders": "自訂請求 Headers",
|
||||||
"customHeadersDescription": "Header 斷行分隔:Header 名稱:值",
|
"customRequestHeadersDescription": "轉發至目標的請求標頭。每行一個:Header-名稱:值",
|
||||||
|
"customResponseHeaders": "自訂回應 Headers",
|
||||||
|
"customResponseHeadersDescription": "回傳給客戶端的回應標頭。每行一個:Header-名稱:值",
|
||||||
"headersValidationError": "Header 必須是格式:Header 名稱:值。",
|
"headersValidationError": "Header 必須是格式:Header 名稱:值。",
|
||||||
"saveHealthCheck": "保存健康檢查",
|
"saveHealthCheck": "保存健康檢查",
|
||||||
"healthCheckSaved": "健康檢查已保存",
|
"healthCheckSaved": "健康檢查已保存",
|
||||||
|
|||||||
@@ -186,7 +186,8 @@ export const resources = pgTable(
|
|||||||
skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, {
|
skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, {
|
||||||
onDelete: "set null"
|
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),
|
proxyProtocol: boolean("proxyProtocol").notNull().default(false),
|
||||||
proxyProtocolVersion: integer("proxyProtocolVersion").default(1),
|
proxyProtocolVersion: integer("proxyProtocolVersion").default(1),
|
||||||
maintenanceModeEnabled: boolean("maintenanceModeEnabled")
|
maintenanceModeEnabled: boolean("maintenanceModeEnabled")
|
||||||
|
|||||||
@@ -202,7 +202,8 @@ export const resources = sqliteTable(
|
|||||||
skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, {
|
skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, {
|
||||||
onDelete: "set null"
|
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" })
|
proxyProtocol: integer("proxyProtocol", { mode: "boolean" })
|
||||||
.notNull()
|
.notNull()
|
||||||
.default(false),
|
.default(false),
|
||||||
|
|||||||
@@ -261,10 +261,18 @@ export async function updatePublicResources(
|
|||||||
resourceData.ssl == undefined || resourceData.ssl == null
|
resourceData.ssl == undefined || resourceData.ssl == null
|
||||||
? true
|
? true
|
||||||
: resourceData.ssl;
|
: resourceData.ssl;
|
||||||
let headers = "";
|
// `headers` is a deprecated alias for `requestHeaders`
|
||||||
if (resourceData.headers) {
|
const mergedRequestHeaders = [
|
||||||
headers = JSON.stringify(resourceData.headers);
|
...(resourceData.headers ?? []),
|
||||||
}
|
...(resourceData.requestHeaders ?? [])
|
||||||
|
];
|
||||||
|
const requestHeaders =
|
||||||
|
mergedRequestHeaders.length > 0
|
||||||
|
? JSON.stringify(mergedRequestHeaders)
|
||||||
|
: null;
|
||||||
|
const responseHeaders = resourceData.responseHeaders?.length
|
||||||
|
? JSON.stringify(resourceData.responseHeaders)
|
||||||
|
: null;
|
||||||
|
|
||||||
if (resourceData.policy) {
|
if (resourceData.policy) {
|
||||||
const isLicensed = await isLicensedOrSubscribed(
|
const isLicensed = await isLicensedOrSubscribed(
|
||||||
@@ -403,7 +411,8 @@ export async function updatePublicResources(
|
|||||||
? resourceData.auth["whitelist-users"].length >
|
? resourceData.auth["whitelist-users"].length >
|
||||||
0
|
0
|
||||||
: false,
|
: false,
|
||||||
headers: headers || null,
|
requestHeaders,
|
||||||
|
responseHeaders,
|
||||||
applyRules:
|
applyRules:
|
||||||
resourceData.rules &&
|
resourceData.rules &&
|
||||||
resourceData.rules.length > 0,
|
resourceData.rules.length > 0,
|
||||||
@@ -599,7 +608,8 @@ export async function updatePublicResources(
|
|||||||
setHostHeader: resourceData["host-header"] || null,
|
setHostHeader: resourceData["host-header"] || null,
|
||||||
tlsServerName:
|
tlsServerName:
|
||||||
resourceData["tls-server-name"] || null,
|
resourceData["tls-server-name"] || null,
|
||||||
headers: headers || null,
|
requestHeaders,
|
||||||
|
responseHeaders,
|
||||||
maintenanceModeEnabled:
|
maintenanceModeEnabled:
|
||||||
resourceData.maintenance?.enabled,
|
resourceData.maintenance?.enabled,
|
||||||
maintenanceModeType: resourceData.maintenance?.type,
|
maintenanceModeType: resourceData.maintenance?.type,
|
||||||
@@ -1222,7 +1232,8 @@ export async function updatePublicResources(
|
|||||||
setHostHeader: resourceData["host-header"] || null,
|
setHostHeader: resourceData["host-header"] || null,
|
||||||
tlsServerName: resourceData["tls-server-name"] || null,
|
tlsServerName: resourceData["tls-server-name"] || null,
|
||||||
ssl: resourceSsl,
|
ssl: resourceSsl,
|
||||||
headers: headers || null,
|
requestHeaders,
|
||||||
|
responseHeaders,
|
||||||
applyRules:
|
applyRules:
|
||||||
resourceData.rules && resourceData.rules.length > 0,
|
resourceData.rules && resourceData.rules.length > 0,
|
||||||
pamMode: resourceData["auth-daemon"]?.pam || "passthrough",
|
pamMode: resourceData["auth-daemon"]?.pam || "passthrough",
|
||||||
|
|||||||
@@ -326,7 +326,9 @@ export const PublicResourceSchema = z
|
|||||||
auth: AuthSchema.optional(),
|
auth: AuthSchema.optional(),
|
||||||
"host-header": z.string().optional(),
|
"host-header": z.string().optional(),
|
||||||
"tls-server-name": 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(),
|
rules: z.array(RuleSchema).optional(),
|
||||||
maintenance: MaintenanceSchema.optional(),
|
maintenance: MaintenanceSchema.optional(),
|
||||||
"auth-daemon": AuthDaemonSchema.optional(),
|
"auth-daemon": AuthDaemonSchema.optional(),
|
||||||
|
|||||||
@@ -96,7 +96,8 @@ export async function getTraefikConfig(
|
|||||||
tlsServerName: resources.tlsServerName,
|
tlsServerName: resources.tlsServerName,
|
||||||
setHostHeader: resources.setHostHeader,
|
setHostHeader: resources.setHostHeader,
|
||||||
enableProxy: resources.enableProxy,
|
enableProxy: resources.enableProxy,
|
||||||
headers: resources.headers,
|
requestHeaders: resources.requestHeaders,
|
||||||
|
responseHeaders: resources.responseHeaders,
|
||||||
proxyProtocol: resources.proxyProtocol,
|
proxyProtocol: resources.proxyProtocol,
|
||||||
proxyProtocolVersion: resources.proxyProtocolVersion,
|
proxyProtocolVersion: resources.proxyProtocolVersion,
|
||||||
wildcard: resources.wildcard,
|
wildcard: resources.wildcard,
|
||||||
@@ -225,7 +226,8 @@ export async function getTraefikConfig(
|
|||||||
setHostHeader: row.setHostHeader,
|
setHostHeader: row.setHostHeader,
|
||||||
enableProxy: row.enableProxy,
|
enableProxy: row.enableProxy,
|
||||||
targets: [],
|
targets: [],
|
||||||
headers: row.headers,
|
requestHeaders: row.requestHeaders,
|
||||||
|
responseHeaders: row.responseHeaders,
|
||||||
proxyProtocol: row.proxyProtocol,
|
proxyProtocol: row.proxyProtocol,
|
||||||
proxyProtocolVersion: row.proxyProtocolVersion ?? 1,
|
proxyProtocolVersion: row.proxyProtocolVersion ?? 1,
|
||||||
path: row.path, // the targets will all have the same path
|
path: row.path, // the targets will all have the same path
|
||||||
@@ -405,7 +407,8 @@ export async function getTraefikConfig(
|
|||||||
|
|
||||||
// Handle custom headers middleware
|
// Handle custom headers middleware
|
||||||
const customHeadersMiddleware = buildCustomHeadersMiddleware(
|
const customHeadersMiddleware = buildCustomHeadersMiddleware(
|
||||||
resource.headers,
|
resource.requestHeaders,
|
||||||
|
resource.responseHeaders,
|
||||||
resource.setHostHeader,
|
resource.setHostHeader,
|
||||||
resource.resourceId
|
resource.resourceId
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,46 +1,78 @@
|
|||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
|
||||||
|
function parseHeaders(
|
||||||
|
headers: string,
|
||||||
|
label: string,
|
||||||
|
resourceId: number
|
||||||
|
): { name: string; value: string }[] {
|
||||||
|
try {
|
||||||
|
return JSON.parse(headers) as {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}[];
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(
|
||||||
|
`Failed to parse ${label} for resource ${resourceId}: ${e}`
|
||||||
|
);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the customRequestHeaders middleware definition for a resource's
|
* Build the custom headers middleware definition for a resource's
|
||||||
* custom headers + setHostHeader config. Returns null when there are no
|
* custom request/response headers + setHostHeader config. Returns null when
|
||||||
* headers to set, so the caller can skip attaching the middleware.
|
* there are no headers to set, so the caller can skip attaching the
|
||||||
|
* middleware.
|
||||||
*/
|
*/
|
||||||
export function buildCustomHeadersMiddleware(
|
export function buildCustomHeadersMiddleware(
|
||||||
headers: string | null | undefined,
|
requestHeaders: string | null | undefined,
|
||||||
|
responseHeaders: string | null | undefined,
|
||||||
setHostHeader: string | null | undefined,
|
setHostHeader: string | null | undefined,
|
||||||
resourceId: number
|
resourceId: number
|
||||||
): { headers: { customRequestHeaders: { [key: string]: string } } } | null {
|
): {
|
||||||
const headersObj: { [key: string]: string } = {};
|
headers: {
|
||||||
|
customRequestHeaders?: { [key: string]: string };
|
||||||
|
customResponseHeaders?: { [key: string]: string };
|
||||||
|
};
|
||||||
|
} | null {
|
||||||
|
const requestHeadersObj: { [key: string]: string } = {};
|
||||||
|
const responseHeadersObj: { [key: string]: string } = {};
|
||||||
|
|
||||||
if (headers) {
|
if (requestHeaders) {
|
||||||
let headersArr: { name: string; value: string }[] = [];
|
parseHeaders(requestHeaders, "requestHeaders", resourceId).forEach(
|
||||||
try {
|
(header) => {
|
||||||
headersArr = JSON.parse(headers) as {
|
requestHeadersObj[header.name] = header.value;
|
||||||
name: string;
|
}
|
||||||
value: string;
|
);
|
||||||
}[];
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn(
|
|
||||||
`Failed to parse headers for resource ${resourceId}: ${e}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
headersArr.forEach((header) => {
|
|
||||||
headersObj[header.name] = header.value;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setHostHeader) {
|
if (setHostHeader) {
|
||||||
headersObj["Host"] = setHostHeader;
|
requestHeadersObj["Host"] = setHostHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(headersObj).length === 0) {
|
if (responseHeaders) {
|
||||||
|
parseHeaders(responseHeaders, "responseHeaders", resourceId).forEach(
|
||||||
|
(header) => {
|
||||||
|
responseHeadersObj[header.name] = header.value;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasRequestHeaders = Object.keys(requestHeadersObj).length > 0;
|
||||||
|
const hasResponseHeaders = Object.keys(responseHeadersObj).length > 0;
|
||||||
|
|
||||||
|
if (!hasRequestHeaders && !hasResponseHeaders) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
headers: {
|
headers: {
|
||||||
customRequestHeaders: headersObj
|
...(hasRequestHeaders && {
|
||||||
|
customRequestHeaders: requestHeadersObj
|
||||||
|
}),
|
||||||
|
...(hasResponseHeaders && {
|
||||||
|
customResponseHeaders: responseHeadersObj
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,7 +133,8 @@ export async function getTraefikConfig(
|
|||||||
tlsServerName: resources.tlsServerName,
|
tlsServerName: resources.tlsServerName,
|
||||||
setHostHeader: resources.setHostHeader,
|
setHostHeader: resources.setHostHeader,
|
||||||
enableProxy: resources.enableProxy,
|
enableProxy: resources.enableProxy,
|
||||||
headers: resources.headers,
|
requestHeaders: resources.requestHeaders,
|
||||||
|
responseHeaders: resources.responseHeaders,
|
||||||
proxyProtocol: resources.proxyProtocol,
|
proxyProtocol: resources.proxyProtocol,
|
||||||
proxyProtocolVersion: resources.proxyProtocolVersion,
|
proxyProtocolVersion: resources.proxyProtocolVersion,
|
||||||
wildcard: resources.wildcard,
|
wildcard: resources.wildcard,
|
||||||
@@ -275,7 +276,8 @@ export async function getTraefikConfig(
|
|||||||
setHostHeader: row.setHostHeader,
|
setHostHeader: row.setHostHeader,
|
||||||
enableProxy: row.enableProxy,
|
enableProxy: row.enableProxy,
|
||||||
targets: [],
|
targets: [],
|
||||||
headers: row.headers,
|
requestHeaders: row.requestHeaders,
|
||||||
|
responseHeaders: row.responseHeaders,
|
||||||
proxyProtocol: row.proxyProtocol,
|
proxyProtocol: row.proxyProtocol,
|
||||||
proxyProtocolVersion: row.proxyProtocolVersion ?? 1,
|
proxyProtocolVersion: row.proxyProtocolVersion ?? 1,
|
||||||
path: row.path, // the targets will all have the same path
|
path: row.path, // the targets will all have the same path
|
||||||
@@ -712,7 +714,8 @@ export async function getTraefikConfig(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const customHeadersMiddleware = buildCustomHeadersMiddleware(
|
const customHeadersMiddleware = buildCustomHeadersMiddleware(
|
||||||
resource.headers,
|
resource.requestHeaders,
|
||||||
|
resource.responseHeaders,
|
||||||
resource.setHostHeader,
|
resource.setHostHeader,
|
||||||
resource.resourceId
|
resource.resourceId
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -53,9 +53,10 @@ async function queryInlinePolicy(resourcePolicyId: number) {
|
|||||||
|
|
||||||
export type GetResourceResponse = Omit<
|
export type GetResourceResponse = Omit<
|
||||||
NonNullable<Awaited<ReturnType<typeof query>>>,
|
NonNullable<Awaited<ReturnType<typeof query>>>,
|
||||||
"headers"
|
"requestHeaders" | "responseHeaders"
|
||||||
> & {
|
> & {
|
||||||
headers: { name: string; value: string }[] | null;
|
requestHeaders: { name: string; value: string }[] | null;
|
||||||
|
responseHeaders: { name: string; value: string }[] | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.registerPath({
|
registry.registerPath({
|
||||||
@@ -186,9 +187,12 @@ export async function getResource(
|
|||||||
return response<GetResourceResponse>(res, {
|
return response<GetResourceResponse>(res, {
|
||||||
data: {
|
data: {
|
||||||
...returnData,
|
...returnData,
|
||||||
headers: returnData.headers
|
requestHeaders: returnData.requestHeaders
|
||||||
? JSON.parse(returnData.headers)
|
? JSON.parse(returnData.requestHeaders)
|
||||||
: returnData.headers
|
: returnData.requestHeaders,
|
||||||
|
responseHeaders: returnData.responseHeaders
|
||||||
|
? JSON.parse(returnData.responseHeaders)
|
||||||
|
: returnData.responseHeaders
|
||||||
},
|
},
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
|
|||||||
@@ -100,6 +100,14 @@ const updateHttpResourceBodySchema = z
|
|||||||
"When no shared resource policy is assigned (resourcePolicyId is null), updates the resource's inline policy. When a shared policy is assigned, this value overrides the shared policy for this resource."
|
"When no shared resource policy is assigned (resourcePolicyId is null), updates the resource's inline policy. When a shared policy is assigned, this value overrides the shared policy for this resource."
|
||||||
),
|
),
|
||||||
headers: z
|
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()
|
||||||
|
.optional(),
|
||||||
|
responseHeaders: z
|
||||||
.array(z.strictObject({ name: z.string(), value: z.string() }))
|
.array(z.strictObject({ name: z.string(), value: z.string() }))
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional(),
|
.optional(),
|
||||||
@@ -163,12 +171,13 @@ const updateHttpResourceBodySchema = z
|
|||||||
)
|
)
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
if (data.headers) {
|
const validHeaderName = /^[a-zA-Z0-9!#$%&'*+\-.^_`|~]+$/;
|
||||||
// HTTP header names must be valid token characters (RFC 7230)
|
const allHeaders = [
|
||||||
const validHeaderName = /^[a-zA-Z0-9!#$%&'*+\-.^_`|~]+$/;
|
...(data.headers ?? []),
|
||||||
return data.headers.every((h) => validHeaderName.test(h.name));
|
...(data.requestHeaders ?? []),
|
||||||
}
|
...(data.responseHeaders ?? [])
|
||||||
return true;
|
];
|
||||||
|
return allHeaders.every((h) => validHeaderName.test(h.name));
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
error: "Header names may only contain valid HTTP token characters (letters, digits, and !#$%&'*+-.^_`|~)."
|
error: "Header names may only contain valid HTTP token characters (letters, digits, and !#$%&'*+-.^_`|~)."
|
||||||
@@ -176,14 +185,13 @@ const updateHttpResourceBodySchema = z
|
|||||||
)
|
)
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
if (data.headers) {
|
const validHeaderValue = /^[\t\x20-\x7E]*$/;
|
||||||
// HTTP header values must be visible ASCII or horizontal whitespace, no control chars (RFC 7230)
|
const allHeaders = [
|
||||||
const validHeaderValue = /^[\t\x20-\x7E]*$/;
|
...(data.headers ?? []),
|
||||||
return data.headers.every((h) =>
|
...(data.requestHeaders ?? []),
|
||||||
validHeaderValue.test(h.value)
|
...(data.responseHeaders ?? [])
|
||||||
);
|
];
|
||||||
}
|
return allHeaders.every((h) => validHeaderValue.test(h.value));
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
error: "Header values may only contain printable ASCII characters and horizontal whitespace."
|
error: "Header values may only contain printable ASCII characters and horizontal whitespace."
|
||||||
@@ -191,16 +199,17 @@ const updateHttpResourceBodySchema = z
|
|||||||
)
|
)
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
if (data.headers) {
|
const templatePattern = /\{\{[^}]+\}\}/;
|
||||||
// Reject Traefik template syntax {{word}} in names or values
|
const allHeaders = [
|
||||||
const templatePattern = /\{\{[^}]+\}\}/;
|
...(data.headers ?? []),
|
||||||
return data.headers.every(
|
...(data.requestHeaders ?? []),
|
||||||
(h) =>
|
...(data.responseHeaders ?? [])
|
||||||
!templatePattern.test(h.name) &&
|
];
|
||||||
!templatePattern.test(h.value)
|
return allHeaders.every(
|
||||||
);
|
(h) =>
|
||||||
}
|
!templatePattern.test(h.name) &&
|
||||||
return true;
|
!templatePattern.test(h.value)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
error: "Header names and values must not contain template expressions such as {{value}}."
|
error: "Header names and values must not contain template expressions such as {{value}}."
|
||||||
@@ -695,13 +704,32 @@ async function updateHttpResource(
|
|||||||
await createCertificate(domainId, fullDomain, db);
|
await createCertificate(domainId, fullDomain, db);
|
||||||
}
|
}
|
||||||
|
|
||||||
let headers = undefined;
|
let requestHeaders = undefined;
|
||||||
if (updateData.headers) {
|
const mergedRequestHeaders = [
|
||||||
headers = JSON.stringify(updateData.headers);
|
...(updateData.headers ?? []),
|
||||||
} else if (updateData.headers === null) {
|
...(updateData.requestHeaders ?? [])
|
||||||
headers = null;
|
];
|
||||||
|
if (
|
||||||
|
updateData.headers !== undefined ||
|
||||||
|
updateData.requestHeaders !== undefined
|
||||||
|
) {
|
||||||
|
requestHeaders =
|
||||||
|
mergedRequestHeaders.length > 0
|
||||||
|
? JSON.stringify(mergedRequestHeaders)
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let responseHeaders = undefined;
|
||||||
|
if (updateData.responseHeaders) {
|
||||||
|
responseHeaders = JSON.stringify(updateData.responseHeaders);
|
||||||
|
} else if (updateData.responseHeaders === null) {
|
||||||
|
responseHeaders = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateData.headers = undefined;
|
||||||
|
updateData.requestHeaders = undefined;
|
||||||
|
updateData.responseHeaders = undefined;
|
||||||
|
|
||||||
if (!isLicensed) {
|
if (!isLicensed) {
|
||||||
updateData.maintenanceModeEnabled = undefined;
|
updateData.maintenanceModeEnabled = undefined;
|
||||||
updateData.maintenanceModeType = undefined;
|
updateData.maintenanceModeType = undefined;
|
||||||
@@ -754,7 +782,7 @@ async function updateHttpResource(
|
|||||||
|
|
||||||
const updatedResource = await db
|
const updatedResource = await db
|
||||||
.update(resources)
|
.update(resources)
|
||||||
.set({ ...resourceOnlyData, headers })
|
.set({ ...resourceOnlyData, requestHeaders, responseHeaders })
|
||||||
.where(eq(resources.resourceId, resource.resourceId))
|
.where(eq(resources.resourceId, resource.resourceId))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
@@ -778,7 +806,7 @@ async function updateHttpResource(
|
|||||||
|
|
||||||
const updatedResource = await db
|
const updatedResource = await db
|
||||||
.update(resources)
|
.update(resources)
|
||||||
.set({ ...updateData, headers })
|
.set({ ...updateData, requestHeaders, responseHeaders })
|
||||||
.where(eq(resources.resourceId, resource.resourceId))
|
.where(eq(resources.resourceId, resource.resourceId))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
|||||||
@@ -119,7 +119,10 @@ function ProxyResourceHttpForm({
|
|||||||
message: t("proxyErrorInvalidHeader")
|
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() }))
|
.array(z.object({ name: z.string(), value: z.string() }))
|
||||||
.nullable()
|
.nullable()
|
||||||
});
|
});
|
||||||
@@ -131,19 +134,21 @@ function ProxyResourceHttpForm({
|
|||||||
ssl: resource.ssl,
|
ssl: resource.ssl,
|
||||||
tlsServerName: resource.tlsServerName || "",
|
tlsServerName: resource.tlsServerName || "",
|
||||||
setHostHeader: resource.setHostHeader || "",
|
setHostHeader: resource.setHostHeader || "",
|
||||||
headers: resource.headers
|
requestHeaders: resource.requestHeaders,
|
||||||
|
responseHeaders: resource.responseHeaders
|
||||||
},
|
},
|
||||||
mode: "onChange"
|
mode: "onChange"
|
||||||
});
|
});
|
||||||
|
|
||||||
const [, formAction, saveLoading] = useActionState(onSubmit, null);
|
const [, formAction, saveLoading] = useActionState(onSubmit, null);
|
||||||
const [headersValid, setHeadersValid] = useState(true);
|
const [requestHeadersValid, setRequestHeadersValid] = useState(true);
|
||||||
|
const [responseHeadersValid, setResponseHeadersValid] = useState(true);
|
||||||
|
|
||||||
async function onSubmit() {
|
async function onSubmit() {
|
||||||
const isValid = await form.trigger();
|
const isValid = await form.trigger();
|
||||||
if (!isValid) return;
|
if (!isValid) return;
|
||||||
|
|
||||||
if (!headersValid) {
|
if (!requestHeadersValid || !responseHeadersValid) {
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: t("settingsErrorUpdate"),
|
title: t("settingsErrorUpdate"),
|
||||||
@@ -162,7 +167,8 @@ function ProxyResourceHttpForm({
|
|||||||
ssl: data.ssl,
|
ssl: data.ssl,
|
||||||
tlsServerName: data.tlsServerName || null,
|
tlsServerName: data.tlsServerName || null,
|
||||||
setHostHeader: data.setHostHeader || null,
|
setHostHeader: data.setHostHeader || null,
|
||||||
headers: data.headers || null
|
requestHeaders: data.requestHeaders || null,
|
||||||
|
responseHeaders: data.responseHeaders || null
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@@ -183,7 +189,8 @@ function ProxyResourceHttpForm({
|
|||||||
ssl: data.ssl,
|
ssl: data.ssl,
|
||||||
tlsServerName: data.tlsServerName || null,
|
tlsServerName: data.tlsServerName || null,
|
||||||
setHostHeader: data.setHostHeader || null,
|
setHostHeader: data.setHostHeader || null,
|
||||||
headers: data.headers || null
|
requestHeaders: data.requestHeaders || null,
|
||||||
|
responseHeaders: data.responseHeaders || null
|
||||||
});
|
});
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
@@ -324,11 +331,11 @@ function ProxyResourceHttpForm({
|
|||||||
<SettingsFormCell span="full">
|
<SettingsFormCell span="full">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="headers"
|
name="requestHeaders"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
{t("customHeaders")}
|
{t("customRequestHeaders")}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<HeadersInput
|
<HeadersInput
|
||||||
@@ -337,14 +344,46 @@ function ProxyResourceHttpForm({
|
|||||||
field.onChange
|
field.onChange
|
||||||
}
|
}
|
||||||
onValidityChange={
|
onValidityChange={
|
||||||
setHeadersValid
|
setRequestHeadersValid
|
||||||
}
|
}
|
||||||
rows={4}
|
rows={4}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
{t(
|
{t(
|
||||||
"customHeadersDescription"
|
"customRequestHeadersDescription"
|
||||||
|
)}
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SettingsFormCell>
|
||||||
|
|
||||||
|
<SettingsFormCell span="full">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="responseHeaders"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t("customResponseHeaders")}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<HeadersInput
|
||||||
|
value={field.value}
|
||||||
|
onChange={
|
||||||
|
field.onChange
|
||||||
|
}
|
||||||
|
onValidityChange={
|
||||||
|
setResponseHeadersValid
|
||||||
|
}
|
||||||
|
rows={4}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
{t(
|
||||||
|
"customResponseHeadersDescription"
|
||||||
)}
|
)}
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@@ -362,7 +401,11 @@ function ProxyResourceHttpForm({
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
loading={saveLoading}
|
loading={saveLoading}
|
||||||
disabled={saveLoading || !headersValid}
|
disabled={
|
||||||
|
saveLoading ||
|
||||||
|
!requestHeadersValid ||
|
||||||
|
!responseHeadersValid
|
||||||
|
}
|
||||||
form="http-settings-form"
|
form="http-settings-form"
|
||||||
>
|
>
|
||||||
{t("saveSettings")}
|
{t("saveSettings")}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ function ProxyResourceProtocolForm({
|
|||||||
message: t("proxyErrorInvalidHeader")
|
message: t("proxyErrorInvalidHeader")
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
headers: z
|
requestHeaders: z
|
||||||
.array(z.object({ name: z.string(), value: z.string() }))
|
.array(z.object({ name: z.string(), value: z.string() }))
|
||||||
.nullable(),
|
.nullable(),
|
||||||
proxyProtocol: z.boolean().optional(),
|
proxyProtocol: z.boolean().optional(),
|
||||||
@@ -124,7 +124,7 @@ function ProxyResourceProtocolForm({
|
|||||||
resolver: zodResolver(proxySettingsSchema),
|
resolver: zodResolver(proxySettingsSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
setHostHeader: resource.setHostHeader || "",
|
setHostHeader: resource.setHostHeader || "",
|
||||||
headers: resource.headers,
|
requestHeaders: resource.requestHeaders,
|
||||||
proxyProtocol: resource.proxyProtocol || false,
|
proxyProtocol: resource.proxyProtocol || false,
|
||||||
proxyProtocolVersion: resource.proxyProtocolVersion || 1
|
proxyProtocolVersion: resource.proxyProtocolVersion || 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1355,7 +1355,7 @@ export function HealthCheckCredenza(props: HealthCheckCredenzaProps) {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
{t(
|
{t(
|
||||||
"customHeaders"
|
"customRequestHeaders"
|
||||||
)}
|
)}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@@ -1379,7 +1379,7 @@ export function HealthCheckCredenza(props: HealthCheckCredenzaProps) {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
{t(
|
{t(
|
||||||
"customHeadersDescription"
|
"customRequestHeadersDescription"
|
||||||
)}
|
)}
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@@ -733,7 +733,7 @@ export function HealthCheckFormFields({
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
{t("customHeaders")}
|
{t("customRequestHeaders")}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<HeadersInput
|
<HeadersInput
|
||||||
@@ -750,7 +750,7 @@ export function HealthCheckFormFields({
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
{t(
|
{t(
|
||||||
"customHeadersDescription"
|
"customRequestHeadersDescription"
|
||||||
)}
|
)}
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
Reference in New Issue
Block a user