diff --git a/backend/src/app/rpc/commands/webhooks.clj b/backend/src/app/rpc/commands/webhooks.clj index 69e9e987d..66ae14965 100644 --- a/backend/src/app/rpc/commands/webhooks.clj +++ b/backend/src/app/rpc/commands/webhooks.clj @@ -12,7 +12,7 @@ [app.db :as db] [app.http.client :as http] [app.rpc.doc :as-alias doc] - [app.rpc.queries.teams :refer [check-edition-permissions!]] + [app.rpc.queries.teams :refer [check-edition-permissions! check-read-permissions!]] [app.util.services :as sv] [app.util.time :as dt] [app.worker :as-alias wrk] @@ -49,7 +49,7 @@ (instance? javax.net.ssl.SSLHandshakeException exception) (ex/raise :type :validation :code :webhook-validation - :hint "ssl-validaton") + :hint "ssl-validation") :else (ex/raise :type :validation @@ -118,3 +118,19 @@ (check-edition-permissions! conn profile-id (:team-id whook)) (db/delete! conn :webhook {:id id}) nil))) + +;; --- Query: Webhooks + +(s/def ::team-id ::us/uuid) +(s/def ::get-webhooks + (s/keys :req-un [::profile-id ::team-id])) + +(def sql:get-webhooks + "select id, uri, mtype, is_active, error_code, error_count + from webhook where team_id = ? order by uri") + +(sv/defmethod ::get-webhooks + [{:keys [pool] :as cfg} {:keys [profile-id team-id]}] + (with-open [conn (db/open pool)] + (check-read-permissions! conn profile-id team-id) + (db/exec! conn [sql:get-webhooks team-id]))) \ No newline at end of file diff --git a/frontend/resources/styles/main/partials/dashboard-team.scss b/frontend/resources/styles/main/partials/dashboard-team.scss index 2082ec361..24c41c34d 100644 --- a/frontend/resources/styles/main/partials/dashboard-team.scss +++ b/frontend/resources/styles/main/partials/dashboard-team.scss @@ -93,7 +93,8 @@ } .dashboard-team-members, -.dashboard-team-invitations { +.dashboard-team-invitations, +.dashboard-team-webhooks { .empty-invitations { height: 156px; max-width: 1040px; @@ -197,6 +198,71 @@ } } } + + &.uri, + &.active { + width: 48%; + min-width: 300px; + } + + &.last-delivery { + display: flex; + justify-content: center; + width: 50px; + position: relative; + .success svg { + fill: $color-primary; + width: 16px; + height: 16px; + } + .failure svg { + fill: $color-warning; + width: 16px; + height: 16px; + } + + .icon-container { + width: 16px; + height: 16px; + overflow-x: visible; + } + + .icon { + padding: 0; + } + } + + .tooltip { + display: none; + position: absolute; + top: -58px; + left: 50%; + transform: translate(-50%, 0); + text-align: center; + + .label { + border-radius: 3px; + color: $color-white; + background-color: $color-black; + white-space: nowrap; + padding: 12px 20px; + } + + .arrow-down { + margin: 0 auto; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-top: 8px solid $color-black; + } + } + + .last-delivery-icon:hover { + .tooltip { + display: block; + } + } } .dropdown { @@ -380,3 +446,96 @@ } } } + +.dashboard-team-webhooks { + display: flex; + flex-direction: column; + align-items: center; + + .webhooks-hero-container { + max-width: 1000px; + width: 100%; + display: flex; + flex-direction: column; + + .upload-button { + width: 100px; + } + + .btn-secondary { + margin-left: 10px; + } + } + + .webhooks-hero { + font-size: $fs14; + + padding: $size-6; + background-color: $color-white; + margin-top: $size-6; + display: flex; + justify-content: space-between; + + .banner { + background-color: unset; + + display: flex; + + .icon { + display: flex; + align-items: center; + padding-left: 0px; + padding-right: 10px; + svg { + fill: $color-info; + } + } + } + + .desc { + h2 { + margin-bottom: $size-4; + color: $color-black; + } + width: 80%; + color: $color-gray-40; + p { + font-size: 16px; + } + } + + .btn-primary { + flex-shrink: 0; + } + } + + .webhooks-empty { + text-align: center; + max-width: 1000px; + width: 100%; + padding: $size-6; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + border: 1px dashed $color-gray-20; + color: $color-gray-40; + margin-top: 12px; + min-height: 136px; + } +} + +.webhooks-modal { + .action-buttons { + gap: 10px; + } + .input-checkbox label { + font-size: 14px; + color: $color-black; + } + + .explain { + font-size: 12px; + color: $color-gray-40; + } +} diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 0b4d26e8d..dd5e27f12 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -149,6 +149,24 @@ (->> (rp/query! :team-invitations {:team-id team-id}) (rx/map team-invitations-fetched)))))) +;; --- EVENT: fetch-team-webhooks + +(defn team-webhooks-fetched + [webhooks] + (ptk/reify ::team-webhooks-fetched + ptk/UpdateEvent + (update [_ state] + (assoc state :dashboard-team-webhooks webhooks)))) + +(defn fetch-team-webhooks + [] + (ptk/reify ::fetch-team-webhooks + ptk/WatchEvent + (watch [_ state _] + (let [team-id (:current-team-id state)] + (->> (rp/command! :get-webhooks {:team-id team-id}) + (rx/map team-webhooks-fetched)))))) + ;; --- EVENT: fetch-projects (defn projects-fetched @@ -522,6 +540,61 @@ (rx/tap on-success) (rx/catch on-error)))))) +(defn delete-team-webhook + [{:keys [id] :as params}] + (us/assert ::us/uuid id) + (ptk/reify ::delete-team-webhook + ptk/WatchEvent + (watch [_ state _] + (let [team-id (:current-team-id state) + params (assoc params :team-id team-id) + {:keys [on-success on-error] + :or {on-success identity + on-error rx/throw}} (meta params)] + (->> (rp/command! :delete-webhook params) + (rx/tap on-success) + (rx/catch on-error)))))) + +(s/def ::mtype + #{"application/json" + "application/x-www-form-urlencoded" + "application/transit+json"}) + +(defn update-team-webhook + [{:keys [id uri mtype is-active] :as params}] + (us/assert ::us/uuid id) + (us/assert ::us/uri uri) + (us/assert ::mtype mtype) + (us/assert ::us/boolean is-active) + (ptk/reify ::update-team-webhook + ptk/WatchEvent + (watch [_ state _] + (let [team-id (:current-team-id state) + params (assoc params :team-id team-id) + {:keys [on-success on-error] + :or {on-success identity + on-error rx/throw}} (meta params)] + (->> (rp/command! :update-webhook params) + (rx/tap on-success) + (rx/catch on-error)))))) + +(defn create-team-webhook + [{:keys [uri mtype is-active] :as params}] + (us/assert ::us/uri uri) + (us/assert ::mtype mtype) + (us/assert ::us/boolean is-active) + (ptk/reify ::create-team-webhook + ptk/WatchEvent + (watch [_ state _] + (let [team-id (:current-team-id state) + params (assoc params :team-id team-id) + {:keys [on-success on-error] + :or {on-success identity + on-error rx/throw}} (meta params)] + (->> (rp/command! :create-webhook params) + (rx/tap on-success) + (rx/catch on-error)))))) + ;; --- EVENT: delete-team (defn delete-team @@ -913,6 +986,14 @@ (let [team-id (:current-team-id state)] (rx/of (rt/nav :dashboard-team-invitations {:team-id team-id})))))) +(defn go-to-team-webhooks + [] + (ptk/reify ::go-to-team-webhooks + ptk/WatchEvent + (watch [_ state _] + (let [team-id (:current-team-id state)] + (rx/of (rt/nav :dashboard-team-webhooks {:team-id team-id})))))) + (defn go-to-team-settings [] (ptk/reify ::go-to-team-settings diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 4bc06f8d0..6998b831e 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -74,6 +74,9 @@ (def dashboard-team-invitations (l/derived :dashboard-team-invitations st/state)) +(def dashboard-team-webhooks + (l/derived :dashboard-team-webhooks st/state)) + (def dashboard-selected-project (l/derived (fn [state] (dm/get-in state [:dashboard-local :selected-project])) diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index 5b1f41d93..5569d827e 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -72,6 +72,7 @@ :dashboard-font-providers :dashboard-team-members :dashboard-team-invitations + :dashboard-team-webhooks :dashboard-team-settings) [:* diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs index be423286f..646859c9f 100644 --- a/frontend/src/app/main/ui/dashboard.cljs +++ b/frontend/src/app/main/ui/dashboard.cljs @@ -25,7 +25,7 @@ [app.main.ui.dashboard.projects :refer [projects-section]] [app.main.ui.dashboard.search :refer [search-page]] [app.main.ui.dashboard.sidebar :refer [sidebar]] - [app.main.ui.dashboard.team :refer [team-settings-page team-members-page team-invitations-page]] + [app.main.ui.dashboard.team :refer [team-settings-page team-members-page team-invitations-page team-webhooks-page]] [app.main.ui.hooks :as hooks] [app.main.ui.icons :as i] [app.util.dom :as dom] @@ -236,6 +236,9 @@ :dashboard-team-invitations [:& team-invitations-page {:team team}] + :dashboard-team-webhooks + [:& team-webhooks-page {:team team}] + :dashboard-team-settings [:& team-settings-page {:team team :profile profile}] diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs index ea23c1908..73df4dcb2 100644 --- a/frontend/src/app/main/ui/dashboard/sidebar.cljs +++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs @@ -245,6 +245,7 @@ [{:keys [team profile] :as props}] (let [go-members #(st/emit! (dd/go-to-team-members)) go-invitations #(st/emit! (dd/go-to-team-invitations)) + go-webhooks #(st/emit! (dd/go-to-team-webhooks)) go-settings #(st/emit! (dd/go-to-team-settings)) members-map (mf/deref refs/dashboard-team-members) @@ -323,6 +324,7 @@ [:ul.dropdown.options-dropdown [:li {:on-click go-members :data-test "team-members"} (tr "labels.members")] [:li {:on-click go-invitations :data-test "team-invitations"} (tr "labels.invitations")] + [:li {:on-click go-webhooks :data-test "team-webhooks"} (tr "labels.webhooks")] [:li {:on-click go-settings :data-test "team-settings"} (tr "labels.settings")] [:hr] (when can-rename? diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index 3967627d9..aa66bd230 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -27,6 +27,7 @@ [app.util.i18n :as i18n :refer [tr]] [beicon.core :as rx] [cljs.spec.alpha :as s] + [cuerdas.core :as str] [rumext.v2 :as mf])) (mf/defc header @@ -35,13 +36,15 @@ (let [go-members (mf/use-fn #(st/emit! (dd/go-to-team-members))) go-settings (mf/use-fn #(st/emit! (dd/go-to-team-settings))) go-invitations (mf/use-fn #(st/emit! (dd/go-to-team-invitations))) - invite-member (mf/use-fn + go-webhooks (mf/use-fn #(st/emit! (dd/go-to-team-webhooks))) + invite-member (mf/use-fn (mf/deps team) #(st/emit! (modal/show {:type :invite-members :team team :origin :team}))) members-section? (= section :dashboard-team-members) settings-section? (= section :dashboard-team-settings) invitations-section? (= section :dashboard-team-invitations) + webhooks-section? (= section :dashboard-team-webhooks) permissions (:permissions team)] [:header.dashboard-header.team @@ -50,6 +53,7 @@ members-section? (tr "labels.members") settings-section? (tr "labels.settings") invitations-section? (tr "labels.invitations") + webhooks-section? (tr "labels.webhooks") :else nil)]] [:nav.dashboard-header-menu [:ul.dashboard-header-options @@ -57,6 +61,8 @@ [:a {:on-click go-members} (tr "labels.members")]] [:li {:class (when invitations-section? "active")} [:a {:on-click go-invitations} (tr "labels.invitations")]] + [:li {:class (when webhooks-section? "active")} + [:a {:on-click go-webhooks} (tr "labels.webhooks")]] [:li {:class (when settings-section? "active")} [:a {:on-click go-settings} (tr "labels.settings")]]]] [:div.dashboard-buttons @@ -571,6 +577,238 @@ [:& invitation-section {:team team :invitations invitations}]]])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; WEBHOOKS SECTION +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(s/def ::uri ::us/not-empty-string) +(s/def ::mtype ::us/not-empty-string) +(s/def ::webhook-form + (s/keys :req-un [::uri ::mtype])) + +(mf/defc webhook-modal {::mf/register modal/components + ::mf/register-as :webhook} + [{:keys [webhook] :as props}] + (let [initial (mf/use-memo (fn [] (or webhook {:is-active false :mtype "application/json"}))) + form (fm/use-form :spec ::webhook-form + :initial initial) + mtypes [{:label "application/json" :value "application/json"} + {:label "application/x-www-form-urlencoded" :value "application/x-www-form-urlencoded"} + {:label "application/transit+json" :value "application/transit+json"}] + + on-success + (fn [message] + (st/emit! (dd/fetch-team-webhooks) + (msg/success message) + (modal/hide))) + + on-error + (fn [message {:keys [type code hint] :as error}] + (let [message (if (and (= type :validation) (= code :webhook-validation)) + (str message " " + (case hint + "ssl-validation" (tr "errors.webhooks.ssl-validation") + "")) ;; TODO Add more error codes when back defines them + message)] + (rx/of (msg/error message)))) + + on-create-submit + (fn [] + (let [mdata {:on-success #(on-success (tr "dashboard.webhooks.create.success")) + :on-error (partial on-error (tr "dashboard.webhooks.create.error"))} + webhook {:uri (get-in @form [:clean-data :uri]) + :mtype (get-in @form [:clean-data :mtype]) + :is-active (get-in @form [:clean-data :is-active])}] + (st/emit! (dd/create-team-webhook (with-meta webhook mdata))))) + + on-update-submit + (fn [] + (let [mdata {:on-success #(on-success (tr "dashboard.webhooks.update.success")) + :on-error (partial on-error (tr "dashboard.webhooks.update.error"))} + webhook (get @form :clean-data)] + (st/emit! (dd/update-team-webhook (with-meta webhook mdata))))) + + on-submit + #(let [data (:clean-data @form)] + (if (:id data) + (on-update-submit) + (on-create-submit)))] + + [:div.modal-overlay + [:div.modal-container.webhooks-modal + [:& fm/form {:form form :on-submit on-submit} + + [:div.modal-header + [:div.modal-header-title + (if webhook + [:h2 (tr "modals.edit-webhook.title")] + [:h2 (tr "modals.create-webhook.title")])] + + [:div.modal-close-button + {:on-click #(st/emit! (modal/hide))} i/close]] + + [:div.modal-content.generic-form + [:div.fields-container + [:div.fields-row + [:& fm/input {:type "text" + :auto-focus? true + :form form + :name :uri + :label (tr "modals.create-webhook.url.label") + :placeholder (tr "modals.create-webhook.url.placeholder")}]] + + [:div.fields-row + [:& fm/select {:options mtypes + :label (tr "dashboard.webhooks.content-type") + :default "application/json" + :name :mtype}]]] + [:div.fields-row + [:div.input-checkbox.check-primary + [:& fm/input {:type "checkbox" + :form form + :name :is-active + :label (tr "dashboard.webhooks.active")}] + ] + [:div.explain (tr "dashboard.webhooks.active.explain")]]] + + + + [:div.modal-footer + [:div.action-buttons + [:input.btn-gray.btn-large + {:type "button" + :value (tr "labels.cancel") + :on-click #(modal/hide!)}] + [:& fm/submit-button + {:label (if webhook + (tr "modals.edit-webhook.submit-label") + (tr "modals.create-webhook.submit-label"))}]]]]]])) + + +(mf/defc webhooks-hero + [] + [:div.banner + [:div.title (tr "labels.webhooks") + [:div.description (tr "dashboard.webhooks.description")]] + [:div.create-container + [:div.create (tr "dashboard.webhooks.create")]]] + + [:div.webhooks-hero-container + [:div.webhooks-hero + [:div.desc + [:h2 (tr "labels.webhooks")] + [:& i18n/tr-html {:label "dashboard.webhooks.description"}]] + + [:div.btn-primary + {:on-click #(st/emit! (modal/show :webhook {}))} + [:span (tr "dashboard.webhooks.create")]]]]) + + + (mf/defc webhook-actions + [{:keys [on-edit on-delete] :as props}] + (let [show? (mf/use-state false)] + [:* + [:span.icon {:on-click #(reset! show? true)} [i/actions]] + [:& dropdown {:show @show? + :on-close #(reset! show? false)} + [:ul.dropdown.actions-dropdown + [:li {:on-click on-edit} (tr "labels.edit")] + [:li {:on-click on-delete} (tr "labels.delete")]]]])) + + (mf/defc last-delivery-icon + [{:keys [success? text] :as props}] + [:div.last-delivery-icon + [:div.tooltip + [:div.label text] + [:div.arrow-down]] + (if success? + [:span.icon.success i/msg-success] + [:span.icon.failure i/msg-warning])]) + + (mf/defc webhook-item + {::mf/wrap [mf/memo]} + [{:keys [webhook] :as props}] + (let [on-edit #(st/emit! (modal/show :webhook {:webhook webhook})) + error-code (:error-code webhook) + extract-status + (fn [error-code] + (let [status (-> error-code + (str/split "-") + last + parse-long)] + (if (nil? status) + "" + status))) + delete-fn + (fn [] + (let [params {:id (:id webhook)} + mdata {:on-success #(st/emit! (dd/fetch-team-webhooks))}] + (st/emit! (dd/delete-team-webhook (with-meta params mdata))))) + on-delete #(st/emit! (modal/show + {:type :confirm + :title (tr "modals.delete-webhook.title") + :message (tr "modals.delete-webhook.message") + :accept-label (tr "modals.delete-webhook.accept") + :on-accept delete-fn})) + last-delivery-text (cond + (nil? error-code) + (tr "webhooks.last-delivery.success") + + (= error-code "ssl-validation") + (str (tr "errors.webhooks.last-delivery") " " (tr "errors.webhooks.ssl-validation")) + + (str/starts-with? error-code "unexpected-status") + (str (tr "errors.webhooks.last-delivery") + " " + (tr "errors.webhooks.unexpected-status" (extract-status error-code))) + + :else + (tr "errors.webhooks.last-delivery"))] + [:div.table-row + [:div.table-field.last-delivery + [:div.icon-container + [:& last-delivery-icon {:success? (nil? error-code) :text last-delivery-text}]]] + [:div.table-field.uri + [:div (:uri webhook)]] + [:div.table-field.active + [:div (if (:is-active webhook) (tr "labels.active") (tr "labels.inactive"))]] + [:div.table-field.actions + [:& webhook-actions {:on-edit on-edit + :on-delete on-delete}]]])) + + +(mf/defc webhooks-list + [{:keys [webhooks] :as props}] + [:div.dashboard-table + [:div.table-rows + (for [webhook webhooks] + [:& webhook-item {:webhook webhook :key (:id webhook)}])]]) + +(mf/defc team-webhooks-page + [{:keys [team] :as props}] + (let [webhooks (mf/deref refs/dashboard-team-webhooks)] + + (mf/with-effect [team] + (dom/set-html-title + (tr "title.team-webhooks" + (if (:is-default team) + (tr "dashboard.your-penpot") + (:name team))))) + + (mf/with-effect [team] + (st/emit! (dd/fetch-team-webhooks))) + + [:* + [:& header {:team team :section :dashboard-team-webhooks}] + [:section.dashboard-container.dashboard-team-webhooks + [:div + [:& webhooks-hero] + (if (empty? webhooks) + [:div.webhooks-empty + [:div (tr "dashboard.webhooks.empty.no-webhooks")] + [:div (tr "dashboard.webhooks.empty.add-one")]] + [:& webhooks-list {:webhooks webhooks}])]]])) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; SETTINGS SECTION ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/ui/routes.cljs b/frontend/src/app/main/ui/routes.cljs index f916a52bc..0f0aefd2f 100644 --- a/frontend/src/app/main/ui/routes.cljs +++ b/frontend/src/app/main/ui/routes.cljs @@ -66,6 +66,7 @@ ["/dashboard/team/:team-id" ["/members" :dashboard-team-members] ["/invitations" :dashboard-team-invitations] + ["/webhooks" :dashboard-team-webhooks] ["/settings" :dashboard-team-settings] ["/projects" :dashboard-projects] ["/search" :dashboard-search] diff --git a/frontend/translations/en.po b/frontend/translations/en.po index a113b8b82..c0c7d771f 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -669,6 +669,51 @@ msgstr "Your name" msgid "dashboard.your-penpot" msgstr "Your Penpot" +msgid "dashboard.webhooks.description" +msgstr "Webhooks are a simple way to allow other websites and apps to be notified when certain events happen at Penpot. We’ll send a POST request to each of the URLs you provide." + +msgid "dashboard.webhooks.create" +msgstr "Create webhook" + +msgid "dashboard.webhooks.empty.no-webhooks" +msgstr "No webhooks created so far." + +msgid "dashboard.webhooks.empty.add-one" +msgstr "Press the button \"Add webhook\" to add one." + +msgid "dashboard.webhooks.content-type" +msgstr "Content type" + +msgid "dashboard.webhooks.active" +msgstr "Is active" + +msgid "dashboard.webhooks.active.explain" +msgstr "When this hook is triggered event details will be delivered" + +msgid "webhooks.last-delivery.success" +msgstr "Last delivery was successfull." + +msgid "errors.webhooks.last-delivery" +msgstr "Last delivery was not successfull." + +msgid "errors.webhooks.ssl-validation" +msgstr "Error on SSL validation." + +msgid "errors.webhooks.unexpected-status" +msgstr "Unexpected status %s" + +msgid "dashboard.webhooks.update.error" +msgstr "Error on updating webhook." + +msgid "dashboard.webhooks.update.success" +msgstr "Webhook updated successfully." + +msgid "dashboard.webhooks.create.error" +msgstr "Error on creating webhook." + +msgid "dashboard.webhooks.create.success" +msgstr "Webhook created successfully." + #: src/app/main/ui/alert.cljs msgid "ds.alert-ok" msgstr "Ok" @@ -1468,6 +1513,15 @@ msgstr "(you)" msgid "labels.your-account" msgstr "Your account" +msgid "labels.webhooks" +msgstr "Webhooks" + +msgid "labels.active" +msgstr "Active" + +msgid "labels.inactive" +msgstr "Inactive" + #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "media.loading" msgstr "Loading image…" @@ -1682,6 +1736,33 @@ msgstr "Are you sure you want to delete this member from the team?" msgid "modals.delete-team-member-confirm.title" msgstr "Delete team member" +msgid "modals.delete-webhook.title" +msgstr "Deleting webhook" + +msgid "modals.delete-webhook.message" +msgstr "Are you sure you want to delete this webhook?" + +msgid "modals.delete-webhook.accept" +msgstr "Delete webhook" + +msgid "modals.create-webhook.title" +msgstr "Create webhook" + +msgid "modals.create-webhook.submit-label" +msgstr "Create webhook" + +msgid "modals.create-webhook.url.label" +msgstr "Payload URL" + +msgid "modals.create-webhook.url.placeholder" +msgstr "https://example.com/postreceive" + +msgid "modals.edit-webhook.title" +msgstr "Edit webhook" + +msgid "modals.edit-webhook.submit-label" +msgstr "Edit webhook" + #: src/app/main/ui/dashboard/team.cljs msgid "modals.invite-member-confirm.accept" msgstr "Send invitation" @@ -2489,6 +2570,9 @@ msgstr "Profile - Penpot" msgid "title.team-invitations" msgstr "Invitations - %s - Penpot" +msgid "title.team-webhooks" +msgstr "Webhooks - %s - Penpot" + #: src/app/main/ui/dashboard/team.cljs msgid "title.team-members" msgstr "Members - %s - Penpot" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 5e9e36dc1..4cc50f736 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -713,6 +713,51 @@ msgstr "Tu nombre" msgid "dashboard.your-penpot" msgstr "Tu Penpot" +msgid "dashboard.webhooks.description" +msgstr "Los webhooks son una forma simple de permitir notificar a otros sitios web y aplicaciones cuando ocurren ciertos eventos en Penpot. Enviaremos una petición POST a cada una de las URLs que indiques." + +msgid "dashboard.webhooks.create" +msgstr "Crear webhook" + +msgid "dashboard.webhooks.empty.no-webhooks" +msgstr "No hay ningún webhook aún." + +msgid "dashboard.webhooks.empty.add-one" +msgstr "Pulsa el botón \"Crear webhook\" para añadir uno." + +msgid "dashboard.webhooks.content-type" +msgstr "Tipo de contenido" + +msgid "dashboard.webhooks.active" +msgstr "Activo" + +msgid "dashboard.webhooks.active.explain" +msgstr "Cuando se active este webhook se enviarán detalles del evento" + +msgid "webhooks.last-delivery.success" +msgstr "El último envío fue correcto." + +msgid "errors.webhooks.last-delivery" +msgstr "Hubo un problema en el último envío." + +msgid "errors.webhooks.ssl-validation" +msgstr "Error en la validación SSL." + +msgid "errors.webhooks.unexpected-status" +msgstr "Estado inesperado %s" + +msgid "dashboard.webhooks.update.error" +msgstr "Error modificando el webhook" + +msgid "dashboard.webhooks.update.success" +msgstr "Webhook modificado con éxito" + +msgid "dashboard.webhooks.create.error" +msgstr "Error creando con éxito" + +msgid "dashboard.webhooks.create.success" +msgstr "Webhook creado con éxito" + #: src/app/main/ui/alert.cljs msgid "ds.alert-ok" msgstr "Ok" @@ -1653,6 +1698,15 @@ msgstr "(tú)" msgid "labels.your-account" msgstr "Tu cuenta" +msgid "labels.webhooks" +msgstr "Webhooks" + +msgid "labels.active" +msgstr "Activo" + +msgid "labels.inactive" +msgstr "Inactivo" + #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "media.loading" msgstr "Cargando imagen…" @@ -1880,6 +1934,33 @@ msgstr "¿Seguro que quieres eliminar este integrante del equipo?" msgid "modals.delete-team-member-confirm.title" msgstr "Eliminar integrante del equipo" +msgid "modals.delete-webhook.title" +msgstr "Borrando webhook" + +msgid "modals.delete-webhook.message" +msgstr "¿Seguro que quieres borrar este webhook?" + +msgid "modals.delete-webhook.accept" +msgstr "Borrar webhook" + +msgid "modals.create-webhook.title" +msgstr "Crear webhook" + +msgid "modals.create-webhook.submit-label" +msgstr "Crear webhook" + +msgid "modals.create-webhook.url.label" +msgstr "Payload URL" + +msgid "modals.create-webhook.url.placeholder" +msgstr "https://example.com/postreceive" + +msgid "modals.edit-webhook.title" +msgstr "Modificar webhook" + +msgid "modals.edit-webhook.submit-label" +msgstr "Modificar webhook" + #: src/app/main/ui/dashboard/team.cljs msgid "modals.invite-member-confirm.accept" msgstr "Enviar invitacion" @@ -2818,6 +2899,9 @@ msgstr "Perfil - Penpot" msgid "title.team-invitations" msgstr "Invitaciones - %s - Penpot" +msgid "title.team-webhooks" +msgstr "Webhooks - %s - Penpot" + #: src/app/main/ui/dashboard/team.cljs msgid "title.team-members" msgstr "Integrantes - %s - Penpot"