1
0
Fork 0
mirror of synced 2024-06-29 11:40:45 +12:00

Merge pull request #3193 from appwrite/feat-new-event-model-ui

feat: new event model UI
This commit is contained in:
Torsten Dittmann 2022-05-13 14:51:48 +02:00 committed by GitHub
commit 633ef3ae4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 626 additions and 261 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -162,8 +162,47 @@ App::get('/console/webhooks')
$page = new View(__DIR__.'/../../views/console/webhooks/index.phtml');
$page->setParam('events', Config::getParam('events', []));
$layout
->setParam('title', APP_NAME.' - Webhooks')
->setParam('body', $page);
});
App::get('/console/webhooks/webhook')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->param('id', '', new UID(), 'Webhook unique ID.')
->inject('layout')
->action(function ($id, $layout) {
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/console/webhooks/webhook.phtml');
$page
->setParam('events', Config::getParam('events', []))
->setParam('new', false)
;
$layout
->setParam('title', APP_NAME.' - Webhooks')
->setParam('body', $page);
});
App::get('/console/webhooks/webhook/new')
->groups(['web', 'console'])
->label('permission', 'public')
->label('scope', 'console')
->inject('layout')
->action(function ($layout) {
/** @var Appwrite\Utopia\View $layout */
$page = new View(__DIR__.'/../../views/console/webhooks/webhook.phtml');
$page
->setParam('events', Config::getParam('events', []))
->setParam('new', true)
;
$layout

View file

@ -1,9 +1,33 @@
<?php
$fileLimit = $this->getParam('fileLimit', 0);
$fileLimitHuman = $this->getParam('fileLimitHuman', 0);
$events = array_keys($this->getParam('events', []));
$events = $this->getParam('events', []);
$timeout = $this->getParam('timeout', 900);
$usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
$patterns = [];
foreach ($events as $name => $event) {
$patterns[] = $name;
foreach ($event as $key => $value) {
if (!\str_starts_with($key, '$')) {
if (!($value['$resource'] ?? false)) {
$patterns[] = "{$name}.{$key}";
} else {
$patterns[] = $key;
foreach ($value as $key2 => $value2) {
if (!\str_starts_with($key2, '$')) {
if (!($value2['$resource'] ?? false)) {
$patterns[] = "{$key}.{$key2}";
}
}
}
}
}
}
}
sort($patterns);
?>
<div
data-service="functions.get"
@ -469,7 +493,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
</div>
</div>
</li>
<li data-state="/console/functions/function/settings?id={{router.params.id}}&project={{router.params.project}}">
<li data-state="/console/functions/function/settings?id={{router.params.id}}&project={{router.params.project}}" x-data="events">
<h2>Settings</h2>
<div class="row responsive margin-top-negative">
@ -507,32 +531,30 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
<div class="text-size-small text-fade margin-bottom margin-top-negative-small">Max value is <?php echo $this->escape(number_format($timeout)); ?> seconds (<?php echo $this->escape((int) ($timeout / 60)); ?> minutes)</div>
</section>
<section class="margin-bottom" data-forms-select-all>
<label for="events" class="margin-bottom">Events <span class="tooltip small" data-tooltip="Choose which events should trigger this function."><i class="icon-info-circled"></i></span></label>
<div class="row responsive thin margin-top-small">
<?php foreach ($events as $i => $event): ?>
<div class="col span-6 text-one-liner margin-bottom text-height-large text-size-small" title="<?php echo $event; ?>">
<input type="checkbox" name="events" data-ls-bind="{{project-function.events}}" id="<?php echo $event; ?>" value="<?php echo $event; ?>" data-by-key="true" />
&nbsp;
<label class="inline" for="<?php echo $event; ?>"><?php echo $event; ?></label>
<section class="margin-bottom-small" data-ls-attrs="x-init=load({{project-function.events}})">
<label class="margin-bottom-small">Events <span class="tooltip small" data-tooltip="Set events that will trigger your function."><i class="icon-info-circled"></i></span></label>
<div>
<template x-for="event in Array.from(events)">
<div class="row events responsive thin margin-bottom-small">
<div class="col span-12 margin-bottom-small">
<span class="text" x-text="event"></span>
<span class="action" @click="removeEvent(event)">
<i class="icon-trash"></i>
</span>
</div>
<input name="events" data-cast-to="array" type="hidden" :value="event"></input>
</div>
<?php if (($i + 1) % 2 === 0): ?>
</div>
<div class="row responsive thin">
<?php endif;?>
<?php endforeach;?>
</template>
</div>
<button class="margin-end margin-bottom-small reverse" type="button" @click="showModal($refs.modal_function)">Add Event</button>
</section>
<label for="schedule">Schedule (CRON Syntax) <span class="tooltip small" data-tooltip="Set a CRON schedule to trigger this function."><i class="icon-info-circled"></i></span></label>
<input type="text" id="function-schedule" class="full-width" name="schedule" autocomplete="off" data-ls-bind="{{project-function.schedule}}" placeholder="* * * * *" />
<div class="text-size-small text-fade margin-bottom margin-top-negative-small">Leave blank for no schedule</div>
<h3 class="margin-bottom-small">Variables <span class="tooltip small" data-tooltip="Set variables or secret keys that will be passed as env vars to your function at runtime."><i class="icon-info-circled"></i></span></h3>
<label class="margin-bottom-small">Variables <span class="tooltip small" data-tooltip="Set variables or secret keys that will be passed as env vars to your function at runtime."><i class="icon-info-circled"></i></span></label>
<div data-ls-if="(!{{project-function.vars.length}})">
<hr class="margin-bottom margin-top-no" />
<fieldset name="vars" data-cast-to="object">
<div data-ls-loop="project-function.vars" data-ls-as="var" id="project-vars" style="visibility: visible;">
<div class="margin-bottom-small">
@ -541,7 +563,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
<input type="hidden" data-forms-key-value data-ls-attrs="name={{$index}}" data-ls-bind="{{var}}" />
</div>
<div class="col span-2">
<button type="button" data-remove class="close pull-end is-margin-top-10"><i class="icon-cancel"></i></button>
<button type="button" data-remove class="close pull-end is-margin-top-10"><i class="icon-trash"></i></button>
</div>
</div>
</div>
@ -554,14 +576,14 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
<input type="hidden" data-ls-attrs="data-forms-key-value"/>
</div>
<div class="col span-2">
<button type="button" data-remove class="close pull-end is-margin-top-10"><i class="icon-cancel"></i></button>
<button type="button" data-remove class="close pull-end is-margin-top-10"><i class="icon-trash"></i></button>
</div>
</div>
</div>
</div>
</fieldset>
<hr class="margin-bottom margin-top-small" />
</div>
<hr class="margin-bottom margin-top-small" />
<button>Update</button>
</div>
@ -601,6 +623,41 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
</form>
</div>
</div>
<div x-ref="modal_function" data-ui-modal class="modal box close width-small height-small" data-button-hide="on">
<div>
<form @submit.prevent="addEvent($refs.modal_function)">
<label for="event">
Event
</label>
<select id="event" x-model="selected" @change="setEvent()">
<option value="" selected>Select event</option>
<?php foreach ($patterns as $event) : ?>
<option value="<?php echo $event; ?>"><?php echo $event; ?></option>
<?php endforeach; ?>
</select>
<div x-show="hasResource">
<label x-text="'Select ' + resourceName + ' (optional)'" for="resource"></label>
<input id="resource" type="text" :placeholder="resourceName" x-model="resource" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$">
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Leave empty for wildcard</div>
</div>
<div x-show="hasSubResource">
<label x-text="'Select ' + subResourceName + ' (optional)'" for="subResource"></label>
<input id="subResource" type="text" :placeholder="subResourceName" x-model="subResource" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$">
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Leave empty for wildcard</div>
</div>
<div x-show="hasAttribute">
<label for="attribute">Add Attribute (optional)</label>
<select id="attribute" x-model="attribute">
<option value="*">Select attribute</option>
<template x-for="attr in attributes">
<option :value="attr" x-text="attr"></option>
</template>
</select>
</div>
<button x-show="selected" type="submit">Add Event</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
</div>
</li>
</ul>
</div>

View file

@ -1,8 +1,28 @@
<?php
$events = array_keys($this->getParam('events', []));
$events = $this->getParam('events', []);
$patterns = [];
foreach ($events as $name => $event) {
foreach ($event as $key => $value) {
if (!\str_starts_with($key, '$')) {
if (!($value['$resource'] ?? false)) {
$patterns[] = "{$name}.{$key}";
} else {
foreach ($value as $key2 => $value2) {
if (!\str_starts_with($key2, '$')) {
if (!($value2['$resource'] ?? false)) {
$patterns[] = "{$key}.{$key2}";
}
}
}
}
}
}
}
?>
<div class="cover margin-bottom-large">
<h1 class="zone xl margin-bottom-large">
<a data-ls-attrs="href=/console/home?project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> Home</a>
@ -30,117 +50,7 @@ $events = array_keys($this->getParam('events', []));
<div class="box margin-bottom" data-ls-if="0 != {{console-webhooks.total}}">
<ul data-ls-loop="console-webhooks.webhooks" data-ls-as="webhook" class="list">
<li class="clear">
<div data-ui-modal class="modal close sticky-footer" data-button-text="Update" data-button-class="pull-end">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Update Webhook</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Update Project Webhook"
data-service="projects.updateWebhook"
data-scope="console"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Updated webhook successfully"
data-success-param-trigger-events="projects.updateWebhook"
data-failure="alert"
data-failure-param-alert-text="Failed to update webhook"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="webhookId" data-ls-bind="{{webhook.$id}}" />
<label data-ls-attrs="for=name-{{webhook.$id}}">Name</label>
<input type="text" class="full-width" data-ls-attrs="id=name-{{webhook.$id}}" name="name" required autocomplete="off" data-ls-bind="{{webhook.name}}" maxlength="128" />
<label data-ls-attrs="for=url-{{webhook.$id}}">POST URL</label>
<input type="url" class="full-width" data-ls-attrs="id=url-{{webhook.$id}}" name="url" required autocomplete="off" placeholder="https://example.com/callback" data-ls-bind="{{webhook.url}}" />
<section data-forms-select-all>
<label data-ls-attrs="for=events-{{webhook.$id}}">Events</label>
<div class="row responsive thin">
<?php foreach ($events as $i => $event): ?>
<div class="col span-6 text-one-liner margin-bottom text-height-large text-size-small" title="<?php echo $event; ?>">
<input type="checkbox" name="events" data-ls-bind="{{webhook.events}}" id="update-<?php echo $event; ?>" value="<?php echo $event; ?>" data-by-key="true" />
&nbsp;
<label class="inline" for="update-<?php echo $event; ?>"><?php echo $event; ?></label>
</div>
<?php if (($i + 1) % 2 === 0): ?>
</div>
<div class="row responsive thin">
<?php endif;?>
<?php endforeach;?>
</div>
</section>
<div class="margin-bottom toggle" data-ls-ui-open data-button-aria="Advanced Options">
<i class="icon-plus pull-end margin-top-tiny"></i>
<i class="icon-minus pull-end margin-top-tiny"></i>
<h2 class="margin-bottom">
Advanced Options
<small class="text-size-small">(optional)</small>
</h2>
<label data-ls-attrs="for=security-{{task.$id}}" class="margin-bottom-small">
<div class="margin-bottom-small text-bold">SSL / TLS (Certificate verification)</div>
<input name="security" type="radio" required data-ls-attrs="id=secure-yes-{{webhook.$id}}" data-ls-bind="{{webhook.security}}" value="true" data-cast-to="boolean" /> &nbsp; <span>Enabled</span> &nbsp;
<input name="security" type="radio" required data-ls-attrs="id=secure-no-{{webhook.$id}}" data-ls-bind="{{webhook.security}}" value="false" data-cast-to="boolean" /> &nbsp; <span>Disabled</span> &nbsp;
</label>
<p class="margin-bottom text-size-small text-fade"><span class="text-red">Warning</span>: Untrusted or self-signed certificates may not be secure.
<a href="https://en.wikipedia.org/wiki/Self-signed_certificate" target="_blank" rel="noopener">Learn more<i class="icon-link-ext"></i></a>
</p>
<label class="text-bold">HTTP Authentication <span class="tooltip" data-tooltip="Use to secure your endpoint from untrusted sources"><i class="icon-question"></i></span> &nbsp;<small>(optional)</small></label>
<div class="row responsive thin">
<div class="col span-6 margin-bottom">
<label data-ls-attrs="for=httpUser-{{webhook.$id}}">User</label>
<input type="text" class="full-width margin-bottom-no" data-ls-attrs="id=httpUser-{{webhook.$id}}" name="httpUser" autocomplete="off" data-ls-bind="{{webhook.httpUser}}" />
</div>
<div class="col span-6 margin-bottom">
<label data-ls-attrs="for=httpPass-{{webhook.$id}}">Password</label>
<input type="password" data-forms-show-secret class="full-width margin-bottom-no" data-ls-attrs="id=httpPass-{{webhook.$id}}" name="httpPass" autocomplete="off" data-ls-bind="{{webhook.httpPass}}" />
</div>
</div>
</div>
<footer>
<button type="submit">Update</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>
<form class="pull-end margin-end"
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Project Webhook"
data-service="projects.deleteWebhook"
data-scope="console"
data-event="submit"
data-confirm="Are you sure you want to delete this webhook?"
data-success="alert,trigger"
data-success-param-alert-text="Deleted webhook successfully"
data-success-param-trigger-events="projects.deleteWebhook"
data-failure="alert"
data-failure-param-alert-text="Failed to delete webhook"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="webhookId" data-ls-bind="{{webhook.$id}}" />
<button class="danger reverse">Delete</button>
</form>
<a data-ls-attrs="href=/console/webhooks/webhook?project={{router.params.project}}&id={{webhook.$id}}" class="button reverse pull-end margin-end">Manage</a>
<span data-ls-bind="{{webhook.name}}"></span> &nbsp; (<span data-ls-bind="{{webhook.events.length}}"></span> events)
<span data-ls-if="false === {{webhook.security}}">
@ -152,93 +62,5 @@ $events = array_keys($this->getParam('events', []));
</li>
</ul>
</div>
<div class="clear">
<div data-ui-modal class="modal close box sticky-footer" data-button-text="Add Webhook">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Add Webhook</h1>
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Project Webhook"
data-service="projects.createWebhook"
data-scope="console"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created webhook successfully"
data-success-param-trigger-events="projects.createWebhook"
data-failure="alert"
data-failure-param-alert-text="Failed to create webhook"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<label for="name">Name</label>
<input type="text" class="full-width" id="name" name="name" required autocomplete="off" maxlength="128" />
<label for="url">POST URL</label>
<input type="url" class="full-width" id="url" name="url" required autocomplete="off" placeholder="https://example.com/callback" />
<section data-forms-select-all>
<label for="events">Events</label>
<div class="row responsive thin">
<?php foreach ($events as $i => $event): ?>
<div class="col span-6 text-one-liner margin-bottom text-height-large text-size-small" title="<?php echo $event; ?>">
<input type="checkbox" name="events" id="add-<?php echo $event; ?>" value="<?php echo $event; ?>" data-by-key="true" />
&nbsp;
<label class="inline" for="add-<?php echo $event; ?>"><?php echo $event; ?></label>
</div>
<?php if (($i + 1) % 2 === 0): ?>
</div>
<div class="row responsive thin">
<?php endif;?>
<?php endforeach;?>
</div>
</section>
<div class="margin-bottom toggle" data-ls-ui-open data-button-aria="Advanced Options">
<i class="icon-plus pull-end margin-top-tiny"></i>
<i class="icon-minus pull-end margin-top-tiny"></i>
<h2 class="margin-bottom">
Advanced Options
<small class="text-size-small">(optional)</small>
</h2>
<label data-ls-attrs="for=security-{{task.$id}}" class="margin-bottom-small">
<div class="margin-bottom-small text-bold">SSL / TLS (Certificate verification)</div>
<input name="security" type="radio" required data-ls-attrs="id=secure-yes" checked="checked" value="1" /> &nbsp; <span>Enabled</span> &nbsp;
<input name="security" type="radio" required data-ls-attrs="id=secure-no" value="0" /> &nbsp; <span>Disabled</span> &nbsp;
</label>
<p class="margin-bottom text-size-small text-fade"><span class="text-red">Warning</span>: Untrusted or self-signed certificates may not be secure.
<a href="https://en.wikipedia.org/wiki/Self-signed_certificate" target="_blank" rel="noopener">Learn more<i class="icon-link-ext"></i></a>
</p>
<label class="text-bold">HTTP Authentication <span class="tooltip" data-tooltip="Use to secure your endpoint from untrusted sources"><i class="icon-question"></i></span> &nbsp;<small>(optional)</small></label>
<div class="row responsive thin">
<div class="col span-6 margin-bottom">
<label for="httpUser">User</label>
<input type="text" class="full-width margin-bottom-no" id="httpUser" name="httpUser" autocomplete="off" />
</div>
<div class="col span-6 margin-bottom">
<label for="httpPass">Password</label>
<input type="password" data-forms-show-secret class="full-width margin-bottom-no" id="httpPass" name="httpPass" autocomplete="off" />
</div>
</div>
</div>
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>
</div>
<a data-ls-attrs="href=/console/webhooks/webhook/new?project={{router.params.project}}" class="button">Add Webhook</a>
</div>

View file

@ -0,0 +1,205 @@
<?php
$new = $this->getParam('new', false);
$events = $this->getParam('events', []);
$patterns = [];
foreach ($events as $name => $event) {
$patterns[] = $name;
foreach ($event as $key => $value) {
if (!\str_starts_with($key, '$')) {
if (!($value['$resource'] ?? false)) {
$patterns[] = "{$name}.{$key}";
} else {
$patterns[] = $key;
foreach ($value as $key2 => $value2) {
if (!\str_starts_with($key2, '$')) {
if (!($value2['$resource'] ?? false)) {
$patterns[] = "{$key}.{$key2}";
}
}
}
}
}
}
}
sort($patterns);
?>
<div
data-service="projects.getWebhook"
data-name="project-webhook"
data-scope="console"
data-event="load,projects.createWebhook, projects.deleteWebhook, projects.updateWebhook"
data-param-project-id="{{router.params.project}}"
data-param-webhook-id="{{router.params.id}}"
data-success="trigger"
data-success-param-trigger-events="projects.getWebhook">
<div class="cover">
<h1 class="zone xl margin-bottom-large">
<a data-ls-attrs="href=/console/webhooks?project={{router.params.project}}" class="back text-size-small link-return-animation--start"><i class="icon-left-open"></i> Webhooks</a>
<br />
<?php if ($new) : ?>
<span>Add Webhook</span>
<? else : ?>
<span data-ls-bind="{{project-webhook.name}}&nbsp;">&nbsp;</span>
<?php endif; ?>
</h1>
</div>
<div class="zone xl" x-data="events">
<h2 class="margin-top">Settings</h2>
<div class="row responsive">
<div class="col span-8 margin-bottom">
<label>&nbsp;</label>
<div class="box margin-bottom-large">
<form
data-analytics
data-analytics-activity
data-analytics-event="submit"
data-analytics-category="console"
<?php if ($new) : ?>
data-success="alert,trigger,redirect"
data-analytics-label="Create Project Webhook"
data-service="projects.createWebhook"
data-success-param-alert-text="Created webhook successfully"
data-success-param-trigger-events="projects.createWebhook"
data-failure-param-alert-text="Failed to create webhook"
data-success-param-redirect-url="/console/webhooks?project={{router.params.project}}"
<?php else : ?>
data-success="alert,trigger"
data-analytics-label="Update Project Webhook"
data-service="projects.updateWebhook"
data-success-param-alert-text="Updated webhook successfully"
data-success-param-trigger-events="projects.updateWebhook"
data-failure-param-alert-text="Failed to update webhook"
<?php endif; ?>
data-scope="console"
data-event="submit"
data-failure="alert"
data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<?php if (!$new) : ?><input type="hidden" name="webhookId" data-ls-bind="{{project-webhook.$id}}" /><?php endif; ?>
<label data-ls-attrs="for=name-{{webhook.$id}}">Name</label>
<input type="text" class="full-width" data-ls-attrs="id=name-{{project-webhook.$id}}" name="name" required autocomplete="off" data-ls-bind="{{project-webhook.name}}" maxlength="128" />
<label data-ls-attrs="for=url-{{webhook.$id}}">POST URL</label>
<input type="url" class="full-width" data-ls-attrs="id=url-{{project-webhook.$id}}" name="url" required autocomplete="off" placeholder="https://example.com/callback" data-ls-bind="{{project-webhook.url}}" />
<section class="margin-bottom-small" data-ls-attrs="x-init=load({{project-webhook.events}})">
<label class="margin-bottom-small">Events <span class="tooltip small" data-tooltip="Set events that will trigger your webhook."><i class="icon-info-circled"></i></span></label>
<div>
<template x-for="event in Array.from(events)">
<div class="row events responsive thin margin-bottom-small">
<div class="col span-12 margin-bottom-small">
<span class="text" x-text="event"></span>
<span class="action" @click="removeEvent(event)">
<i class="icon-trash"></i>
</span>
</div>
<input name="events" data-cast-to="array" type="hidden" :value="event"></input>
</div>
</template>
</div>
<button class="margin-end margin-bottom-small reverse" type="button" @click="showModal($refs.modal_webhook)">Add Event</button>
</section>
<div class="margin-bottom toggle" data-ls-ui-open data-button-aria="Advanced Options">
<i class="icon-plus pull-end margin-top-tiny"></i>
<i class="icon-minus pull-end margin-top-tiny"></i>
<h2 class="margin-bottom">
Advanced Options
<small class="text-size-small">(optional)</small>
</h2>
<label data-ls-attrs="for=security-{{project-webhook.$id}}" class="margin-bottom-small">
<div class="margin-bottom-small">SSL / TLS (Certificate verification)</div>
<input <?php if ($new): ?>checked<?php else: ?>data-ls-bind="{{project-webhook.security}}"<?php endif; ?> name="security" type="radio" required data-ls-attrs="id=secure-yes-{{project-webhook.$id}}" value="true" data-cast-to="boolean" /> &nbsp; <span>Enabled</span> &nbsp;
<input name="security" type="radio" required data-ls-attrs="id=secure-no-{{project-webhook.$id}}" data-ls-bind="{{project-webhook.security}}" value="false" data-cast-to="boolean" /> &nbsp; <span>Disabled</span> &nbsp;
</label>
<p class="margin-bottom text-size-small text-fade"><span class="text-red">Warning</span>: Untrusted or self-signed certificates may not be secure.
<a href="https://en.wikipedia.org/wiki/Self-signed_certificate" target="_blank" rel="noopener">Learn more<i class="icon-link-ext"></i></a>
</p>
<label>HTTP Authentication <span class="tooltip" data-tooltip="Use to secure your endpoint from untrusted sources"><i class="icon-question"></i></span> &nbsp;<small>(optional)</small></label>
<div class="row responsive thin">
<div class="col span-6 margin-bottom">
<label data-ls-attrs="for=httpUser-{{project-webhook.$id}}">User</label>
<input type="text" class="full-width margin-bottom-no" data-ls-attrs="id=httpUser-{{project-webhook.$id}}" name="httpUser" autocomplete="off" data-ls-bind="{{project-webhook.httpUser}}" />
</div>
<div class="col span-6 margin-bottom">
<label data-ls-attrs="for=httpPass-{{project-webhook.$id}}">Password</label>
<input type="password" data-forms-show-secret class="full-width margin-bottom-no" data-ls-attrs="id=httpPass-{{project-webhook.$id}}" name="httpPass" autocomplete="off" data-ls-bind="{{project-webhook.httpPass}}" />
</div>
</div>
</div>
<footer>
<button type="submit">Update</button>
</footer>
</form>
</div>
</div>
<?php if (!$new) : ?>
<div class="col span-4 sticky-top margin-bottom">
<label>Webhook ID</label>
<div class="input-copy margin-bottom">
<input id="uid" type="text" autocomplete="off" placeholder="" data-ls-bind="{{project-webhook.$id}}" disabled data-forms-copy>
</div>
<form class="margin-bottom" data-analytics data-analytics-activity data-analytics-event="submit" data-analytics-category="console" data-analytics-label="Delete Project Webhook" data-service="projects.deleteWebhook" data-scope="console" data-event="submit" data-confirm="Are you sure you want to delete this webhook?" data-success="alert,redirect" data-success-param-redirect-url="/console/webhooks?project={{router.params.project}}" data-success-param-alert-text="Deleted webhook successfully" data-success-param-trigger-events="projects.deleteWebhook" data-failure="alert" data-failure-param-alert-text="Failed to delete webhook" data-failure-param-alert-classname="error">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="webhookId" data-ls-bind="{{project-webhook.$id}}" />
<button class="danger fill">Delete Webhook</button>
</form>
</div>
<?php endif; ?>
<div x-ref="modal_webhook" data-ui-modal class="modal box close width-small height-small" data-button-hide="on">
<div>
<form @submit.prevent="addEvent($refs.modal_webhook)">
<label for="event">
Event
</label>
<select id="event" x-model="selected" @change="setEvent()">
<option value="" selected>Select event</option>
<?php foreach ($patterns as $event) : ?>
<option value="<?php echo $event; ?>"><?php echo $event; ?></option>
<?php endforeach; ?>
</select>
<div x-show="hasResource">
<label x-text="'Select ' + resourceName + ' (optional)'" for="resource"></label>
<input id="resource" type="text" :placeholder="resourceName" x-model="resource" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$">
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Leave empty for wildcard</div>
</div>
<div x-show="hasSubResource">
<label x-text="'Select ' + subResourceName + ' (optional)'" for="subResource"></label>
<input id="subResource" type="text" :placeholder="subResourceName" x-model="subResource" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{0,35}$">
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Leave empty for wildcard</div>
</div>
<div x-show="hasAttribute">
<label for="attribute">Add Attribute (optional)</label>
<select id="attribute" x-model="attribute">
<option value="*">Select attribute</option>
<template x-for="attr in attributes">
<option :value="attr" x-text="attr"></option>
</template>
</select>
</div>
<button x-show="selected" type="submit">Add Event</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</form>
</div>
</div>
</div>
</div>
</div>

2
composer.lock generated
View file

@ -6576,5 +6576,5 @@
"platform-overrides": {
"php": "8.0"
},
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.1.0"
}

View file

@ -34,6 +34,7 @@ const configApp = {
'public/scripts/filters.js',
'public/scripts/app.js',
'public/scripts/upload-modal.js',
'public/scripts/events.js',
'public/scripts/views/service.js',

32
package-lock.json generated
View file

@ -2950,6 +2950,15 @@
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"node_modules/meow/node_modules/trim-newlines": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
"integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/micromatch": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
@ -4601,18 +4610,6 @@
"xtend": "~4.0.1"
}
},
"node_modules/trim-newlines": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.0.2.tgz",
"integrity": "sha512-GJtWyq9InR/2HRiLZgpIKv+ufIKrVrvjQWEj7PxAXNc5dwbNJkqhAUoAGgzRmULAnoOM5EIpveYd3J2VeSAIew==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@ -7490,6 +7487,12 @@
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"trim-newlines": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
"integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
"dev": true
}
}
},
@ -8837,11 +8840,6 @@
}
}
},
"trim-newlines": {
"version": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.0.2.tgz",
"integrity": "sha512-GJtWyq9InR/2HRiLZgpIKv+ufIKrVrvjQWEj7PxAXNc5dwbNJkqhAUoAGgzRmULAnoOM5EIpveYd3J2VeSAIew==",
"dev": true
},
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -5223,6 +5223,7 @@
/**
* Get User Memberships
*
* Get the user membership list by its unique ID.
*
* @param {string} userId
* @throws {AppwriteException}

198
public/scripts/events.js Normal file
View file

@ -0,0 +1,198 @@
(function (window) {
document.addEventListener('alpine:init', () => {
Alpine.data('events', () => ({
events: new Set(),
selected: null,
action: null,
type: null,
subType: null,
resource: null,
resourceName: '',
subResource: null,
subResourceName: '',
hasResource: false,
hasSubResource: false,
attribute: null,
hasAttribute: false,
attributes: [],
load(events) {
this.events = new Set(events);
},
reset() {
this.hasResource = this.hasSubResource = this.hasAttribute = false;
this.type = this.subType = this.subResource = this.resource = this.attribute = this.selected = this.action = null;
},
setEvent() {
this.hasResource = this.hasSubResource = this.hasAttribute = this.action = false;
if (!this.selected) {
this.reset();
return;
}
let [type, action] = this.selected.split('.');
switch (type) {
case 'users':
if (action === 'update') {
this.hasAttribute = true;
this.attributes = ['email', 'name', 'password', 'status', 'prefs']
}
this.hasResource = true;
this.type = type;
this.resourceName = 'User ID';
break;
case 'collections':
this.hasResource = true;
this.type = type;
this.resourceName = 'Collection ID';
break;
case 'teams':
this.hasResource = true;
this.type = type;
this.resourceName = 'Team ID';
break;
case 'buckets':
this.hasResource = true;
this.type = type;
this.resourceName = 'Bucket ID';
break;
case 'functions':
this.hasResource = true;
this.type = type;
this.resourceName = 'Function ID';
break;
case 'sessions':
this.hasResource = this.hasSubResource = true;
this.type = 'users';
this.subType = type;
this.resourceName = 'User ID';
this.subResourceName = 'Session ID';
break;
case 'verification':
this.hasResource = this.hasSubResource = true;
this.type = 'users';
this.subType = type;
this.resourceName = 'User ID';
this.subResourceName = 'Verification ID';
break;
case 'recovery':
this.hasResource = this.hasSubResource = true;
this.type = 'users';
this.subType = type;
this.resourceName = 'User ID';
this.subResourceName = 'Recovery ID';
break;
case 'documents':
this.hasResource = this.hasSubResource = true;
this.type = 'collections';
this.subType = type;
this.resourceName = 'Collection ID';
this.subResourceName = 'Document ID';
break;
case 'attributes':
this.hasResource = this.hasSubResource = true;
this.type = 'collections';
this.subType = type;
this.resourceName = 'Collection ID';
this.subResourceName = 'Attribute ID';
break;
case 'indexes':
this.hasResource = this.hasSubResource = true;
this.type = 'collections';
this.subType = type;
this.resourceName = 'Collection ID';
this.subResourceName = 'Index ID';
break;
case 'files':
this.hasResource = this.hasSubResource = true;
this.type = 'buckets';
this.subType = type;
this.resourceName = 'Bucket ID';
this.subResourceName = 'File ID';
break;
case 'memberships':
if (action === 'update') {
this.hasAttribute = true;
this.attributes = ['status']
}
this.hasResource = this.hasSubResource = true;
this.type = 'teams';
this.subType = type;
this.resourceName = 'Team ID';
this.subResourceName = 'Membership ID';
break;
case 'executions':
this.hasResource = this.hasSubResource = true;
this.type = 'functions';
this.subType = type;
this.resourceName = 'Function ID';
this.subResourceName = 'Execution ID';
break;
case 'deployments':
this.hasResource = this.hasSubResource = true;
this.type = 'functions';
this.subType = type;
this.resourceName = 'Function ID';
this.subResourceName = 'Deployment ID';
break;
default:
this.hasResource = true;
this.hasSubResource = true;
break;
}
this.action = action;
},
showModal(modal) {
document.documentElement.classList.add("modal-open");
modal.classList.remove("close");
modal.classList.add("open");
},
closeModal(modal) {
document.documentElement.classList.remove("modal-open");
modal.classList.add("close");
modal.classList.remove("open");
},
addEvent(modal) {
this.closeModal(modal);
let event = `${this.type}.${this.resource ? this.resource : '*'}`;
if (this.hasSubResource) {
event += `.${this.subType}.${this.subResource ? this.subResource : '*'}`;
}
if (this.action) {
event += `.${this.action}`;
}
if (this.attribute) {
event += `.${this.attribute}`;
}
this.events.add(event);
this.reset();
},
removeEvent(value) {
this.events.delete(value);
}
}));
});
})(window);

View file

@ -83,8 +83,17 @@ window.ls.router
scope: "console",
project: true
})
.add("/console/webhooks/:tab", {
template: "/console/webhooks?version=" + APP_ENV.CACHEBUSTER,
.add("/console/webhooks/webhook", {
template: function(window) {
return window.location.pathname + window.location.search + '&version=' + APP_ENV.CACHEBUSTER;
},
scope: "console",
project: true
})
.add("/console/webhooks/webhook/new", {
template: function(window) {
return window.location.pathname + window.location.search + '&version=' + APP_ENV.CACHEBUSTER;
},
scope: "console",
project: true
})

View file

@ -0,0 +1,20 @@
.events.row {
border-bottom: solid 1px var(--config-color-fade-super);
.col {
span.text {
display: inline-block;
width: calc(100% - 28px);
word-break: break-all;
}
span.action {
.pull-end;
cursor: pointer;
}
}
&:last-child {
border-bottom: none;
}
}

View file

@ -29,6 +29,7 @@ img[src=""] {
@import "comps/box";
@import "comps/cover";
@import "comps/database";
@import "comps/events";
@import "comps/footer";
@import "comps/loader";
@import "comps/modal";