🎉 Show dedicated screen when Nitrate is unavailable

This commit is contained in:
Juanfran
2026-05-19 15:07:02 +02:00
parent 595ec599c6
commit 328058ba0e
10 changed files with 124 additions and 14 deletions

View File

@@ -144,6 +144,14 @@
{::yres/status 404
::yres/body (ex-data err)})
(defmethod handle-error :nitrate-unavailable
[err request _]
(binding [l/*context* (request->context request)]
(l/warn :hint "nitrate is unreachable; blocking request" :cause err)
{::yres/status 503
::yres/body (-> (ex-data err)
(assoc :type :nitrate-unavailable))}))
(defmethod handle-error :internal
[error request parent-cause]
(binding [l/*context* (request->context request)]

View File

@@ -61,13 +61,26 @@
(let [response (handler)
status (:status response)]
(when-not status
(l/error :hint "could't do the nitrate request, it is probably down"
(l/error :hint "couldn't do the nitrate request, it is probably down"
:uri uri)
;; TODO decide what to do when Nitrate is inaccesible
nil)
(ex/raise :type :nitrate-unavailable
:hint (str "nitrate is unreachable at " uri)))
(cond
(>= status 500)
;; Nitrate is up enough to answer (or the proxy is) but the
;; service itself is failing; treat as unavailable so callers
;; surface the static error page.
(do
(l/error :hint "nitrate request failed with server error status"
:uri uri
:status status
:body (:body response))
(ex/raise :type :nitrate-unavailable
:status status
:hint (str "nitrate is unavailable, HTTP " status " at " uri)))
(>= status 400)
;; For error status codes (4xx, 5xx), fail immediately without validation
;; For client error status codes (4xx), fail immediately without validation
(do
(when (not= status 404) ;; Don't need to log 404
(l/error :hint "nitrate request failed with error status"
@@ -434,21 +447,27 @@
(defn add-nitrate-licence-to-profile
"Enriches a profile map with subscription information from Nitrate.
Adds a :subscription field containing the user's license details.
Returns the original profile unchanged if the request fails."
Returns the original profile unchanged if the request fails for a reason
other than Nitrate being unreachable. When Nitrate is unreachable the
`:nitrate-unavailable` exception propagates so the request is rejected."
[cfg profile]
(try
(let [subscription (call cfg :get-subscription {:profile-id (:id profile)})]
(assoc profile :subscription subscription))
(catch Throwable cause
(l/error :hint "failed to get nitrate licence"
:profile-id (:id profile)
:cause cause)
profile)))
(if (= :nitrate-unavailable (-> cause ex-data :type))
(throw cause)
(do
(l/error :hint "failed to get nitrate licence"
:profile-id (:id profile)
:cause cause)
profile)))))
(defn add-org-info-to-team
"Enriches a team map with organization information from Nitrate.
Adds organization-id, organization-name, organization-slug, organization-owner-id, and your-penpot fields.
Returns the original team unchanged if the request fails or org data is nil."
Returns the original team unchanged if the request fails or org data is nil.
Propagates `:nitrate-unavailable` so the request is rejected when Nitrate is unreachable."
[cfg team params]
(try
(let [params (assoc (or params {}) :team-id (:id team))
@@ -461,10 +480,13 @@
(assoc :is-default (or (:is-default team) (true? (:is-your-penpot team-with-org)))))
team))
(catch Throwable cause
(l/error :hint "failed to get team organization info"
:team-id (:id team)
:cause cause)
team)))
(if (= :nitrate-unavailable (-> cause ex-data :type))
(throw cause)
(do
(l/error :hint "failed to get team organization info"
:team-id (:id team)
:cause cause)
team)))))
(defn set-team-organization
"Associates a team with an organization in Nitrate.

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1466.16 8469.63 101.36 30.92" fill="currentColor">
<path d="M-1448.93359375,8500.544921875L-1466.1591796875,8493.12890625L-1454.9228515625,8478.05078125L-1430.9052734375,8471.634765625L-1424.8349609375,8494.107421875L-1448.93359375,8500.544921875M-1457.3271484375,8492.91015625L-1450.6474609375,8495.79296875L-1454.0634765625,8483.091796875L-1458.3544921875,8488.85546875L-1457.3271484375,8492.91015625M-1451.5966796875,8481.1484375L-1449.9765625,8487.171875L-1428.296875,8481.37890625L-1429.890625,8475.349609375L-1451.5966796875,8481.1484375M-1449.1455078125,8490.2626953125L-1447.5341796875,8496.25390625L-1425.826171875,8490.455078125L-1427.44921875,8484.466796875L-1449.1455078125,8490.2626953125"/>
<path d="M-1366.314453125,8469.626953125L-1412.9775390625,8469.626953125L-1413.001953125,8492.8994140625L-1366.2548828125,8492.8984375C-1366.2548828125,8492.8984375,-1364.8046875,8489.115234375,-1364.82421875,8481.5634765625C-1364.8447265625,8473.6005859375,-1366.314453125,8469.626953125,-1366.314453125,8469.626953125M-1370.208984375,8473.4765625L-1370.208984375,8479.7119140625L-1412.96484375,8479.7119140625L-1412.9921875,8473.4765625L-1370.208984375,8473.4765625M-1370.208984375,8482.912109375L-1370.208984375,8489.115234375L-1412.9931640625,8489.115234375L-1412.9814453125,8482.912109375L-1370.208984375,8482.912109375"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -354,6 +354,7 @@
(derive :not-found ::exceptional-state)
(derive :bad-gateway ::exceptional-state)
(derive :service-unavailable ::exceptional-state)
(derive :nitrate-unavailable ::exceptional-state)
(defmethod ptk/handle-error ::exceptional-state
[error]

View File

@@ -86,6 +86,10 @@
(= 502 status)
(rx/throw (ex-info "http error" {:type :bad-gateway}))
(and (= 503 status)
(= :nitrate-unavailable (:type body)))
(rx/throw (ex-info "http error" {:type :nitrate-unavailable}))
(= 503 status)
(rx/throw (ex-info "http error" {:type :service-unavailable}))

View File

@@ -18,6 +18,7 @@
(def ^:svg-id brand-google "brand-google")
(def ^:svg-id loader "loader")
(def ^:svg-id logo-error-screen "logo-error-screen")
(def ^:svg-id logo-nitrate-unavailable "logo-nitrate-unavailable")
(def ^:svg-id logo-subscription "logo-subscription")
(def ^:svg-id logo-subscription-light "logo-subscription-light")
(def ^:svg-id nitrate-welcome "nitrate-welcome")

View File

@@ -320,6 +320,16 @@
[:div {:class (stl/css :sign-info)}
[:button {:on-click on-click} (tr "labels.retry")]]]))
(mf/defc nitrate-unavailable*
[]
[:section {:class (stl/css :nitrate-unavailable-layout)}
[:div {:class (stl/css :nitrate-unavailable-content)}
[:> raw-svg* {:id "logo-nitrate-unavailable" :class (stl/css :nitrate-unavailable-logo)}]
[:p {:class (stl/css :nitrate-unavailable-message)}
(tr "labels.nitrate-unavailable.main-message")]]
[:p {:class (stl/css :nitrate-unavailable-footer)}
(tr "labels.copyright-period")]])
(mf/defc webgl-context-lost*
[]
(let [on-reload (mf/use-fn #(js/location.reload))]
@@ -491,6 +501,9 @@
:service-unavailable
[:> service-unavailable*]
:nitrate-unavailable
[:> nitrate-unavailable*]
[:> internal-error* props])))
(mf/defc context-wrapper*

View File

@@ -13,6 +13,51 @@
background-color: var(--color-background-secondary);
}
.nitrate-unavailable-layout {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
background-color: var(--color-background-primary);
padding: var(--sp-xxxl);
}
.nitrate-unavailable-content {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--sp-xxxl);
}
.nitrate-unavailable-logo {
inline-size: 146px;
block-size: 64px;
color: var(--color-foreground-primary);
transform: scale(1,-1);
}
.nitrate-unavailable-message {
@include t.use-typography("title-large");
margin: 0;
max-inline-size: 32rem;
text-align: center;
color: var(--color-foreground-primary);
font-weight: 500;
}
.nitrate-unavailable-footer {
@include t.use-typography("title-medium");
margin: 0;
text-align: center;
color: var(--color-foreground-secondary);
}
.deco-before,
.deco-after {
position: absolute;

View File

@@ -2906,6 +2906,12 @@ msgstr "New password"
msgid "labels.next"
msgstr "Next"
#: src/app/main/ui/static.cljs:325
msgid "labels.nitrate-unavailable.main-message"
msgstr ""
"Penpot is temporarily unavailable due to a system issue. Please try again "
"shortly."
#: src/app/main/ui/dashboard/comments.cljs:122, src/app/main/ui/workspace/comments.cljs:162
msgid "labels.no-comments-available"
msgstr "You're all caught up! New comment notifications will appear here."

View File

@@ -2815,6 +2815,12 @@ msgstr "Nueva contraseña"
msgid "labels.next"
msgstr "Siguiente"
#: src/app/main/ui/static.cljs:325
msgid "labels.nitrate-unavailable.main-message"
msgstr ""
"Penpot no está disponible temporalmente debido a un problema del sistema. "
"Por favor, inténtalo de nuevo en unos momentos."
#: src/app/main/ui/dashboard/comments.cljs:122, src/app/main/ui/workspace/comments.cljs:162
msgid "labels.no-comments-available"
msgstr "¡Ya estás al día! Nuevas notificaciones de comentarios aparecerán aquí."