summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorblizzz <blizzz@arthur-schiwon.de>2019-09-10 16:05:17 +0200
committerGitHub <noreply@github.com>2019-09-10 16:05:17 +0200
commitbfec3715eef9878fe8ba0d27087b104c6b2e75dd (patch)
tree0fcbc976cf0f03b49e3593e483ee828cc24c1fe9
parentf889ea83739448bc39aa39cf64c9ddd8548c7701 (diff)
parent7683208dfa4cf5f0ff196ee0cabdacf8046592eb (diff)
downloadnextcloud-server-bfec3715eef9878fe8ba0d27087b104c6b2e75dd.tar.gz
nextcloud-server-bfec3715eef9878fe8ba0d27087b104c6b2e75dd.zip
Merge pull request #16706 from nextcloud/workflow-frontend
Workflow frontend overhaul
-rw-r--r--apps/workflowengine/lib/Manager.php2
-rw-r--r--apps/workflowengine/src/admin.js385
-rw-r--r--apps/workflowengine/src/components/Check.vue154
-rw-r--r--apps/workflowengine/src/components/Checks/FileMimeType.vue108
-rw-r--r--apps/workflowengine/src/components/Checks/FileSystemTag.vue73
-rw-r--r--apps/workflowengine/src/components/Checks/MultiselectTag/MultiselectTag.vue127
-rw-r--r--apps/workflowengine/src/components/Checks/MultiselectTag/api.js90
-rw-r--r--apps/workflowengine/src/components/Checks/MultiselectTag/index.js4
-rw-r--r--apps/workflowengine/src/components/Checks/RequestTime.vue94
-rw-r--r--apps/workflowengine/src/components/Checks/RequestURL.vue137
-rw-r--r--apps/workflowengine/src/components/Checks/RequestUserAgent.vue133
-rw-r--r--apps/workflowengine/src/components/Checks/RequestUserGroup.vue77
-rw-r--r--apps/workflowengine/src/components/Checks/file.js105
-rw-r--r--apps/workflowengine/src/components/Checks/index.js26
-rw-r--r--apps/workflowengine/src/components/Checks/request.js71
-rw-r--r--apps/workflowengine/src/components/Event.vue105
-rw-r--r--apps/workflowengine/src/components/Operation.vue109
-rw-r--r--apps/workflowengine/src/components/Rule.vue249
-rw-r--r--apps/workflowengine/src/components/Workflow.vue126
-rw-r--r--apps/workflowengine/src/css/multiselect.css11
-rw-r--r--apps/workflowengine/src/filemimetypeplugin.js72
-rw-r--r--apps/workflowengine/src/filenameplugin.js78
-rw-r--r--apps/workflowengine/src/filesizeplugin.js56
-rw-r--r--apps/workflowengine/src/filesystemtagsplugin.js78
-rw-r--r--apps/workflowengine/src/hbs_helpers/getOperators.js7
-rw-r--r--apps/workflowengine/src/hbs_helpers/selectItem.js7
-rw-r--r--apps/workflowengine/src/helpers/api.js30
-rw-r--r--apps/workflowengine/src/helpers/validators.js48
-rw-r--r--apps/workflowengine/src/mixins/valueMixin.js54
-rw-r--r--apps/workflowengine/src/requestremoteaddressplugin.js83
-rw-r--r--apps/workflowengine/src/requesttimeplugin.js196
-rw-r--r--apps/workflowengine/src/requesturlplugin.js117
-rw-r--r--apps/workflowengine/src/requestuseragentplugin.js119
-rw-r--r--apps/workflowengine/src/store.js164
-rw-r--r--apps/workflowengine/src/templates/operation.handlebars45
-rw-r--r--apps/workflowengine/src/templates/operations.handlebars2
-rw-r--r--apps/workflowengine/src/usergroupmembershipplugin.js75
-rw-r--r--apps/workflowengine/src/workflowengine.js82
-rw-r--r--apps/workflowengine/templates/settings.php2
-rw-r--r--apps/workflowengine/webpack.js12
-rw-r--r--package-lock.json336
-rw-r--r--package.json2
42 files changed, 2231 insertions, 1620 deletions
diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php
index efe6c387059..07438b2f7cb 100644
--- a/apps/workflowengine/lib/Manager.php
+++ b/apps/workflowengine/lib/Manager.php
@@ -425,7 +425,7 @@ class Manager implements IManager {
* @param string $operation
* @throws \UnexpectedValueException
*/
- protected function validateOperation($class, $name, array $checks, $operation, string $entity, array $events) {
+ public function validateOperation($class, $name, array $checks, $operation, string $entity, array $events) {
try {
/** @var IOperation $instance */
$instance = $this->container->query($class);
diff --git a/apps/workflowengine/src/admin.js b/apps/workflowengine/src/admin.js
deleted file mode 100644
index 92f485a8b4c..00000000000
--- a/apps/workflowengine/src/admin.js
+++ /dev/null
@@ -1,385 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-import OperationTemplate from './templates/operation.handlebars';
-import OperationsTemplate from './templates/operations.handlebars';
-
-(function() {
- OCA.WorkflowEngine = _.extend(OCA.WorkflowEngine || {}, {
- availablePlugins: [],
- availableChecks: [],
-
- getCheckByClass: function(className) {
- var length = OCA.WorkflowEngine.availableChecks.length;
- for (var i = 0; i < length; i++) {
- if (OCA.WorkflowEngine.availableChecks[i]['class'] === className) {
- return OCA.WorkflowEngine.availableChecks[i];
- }
- }
- return undefined;
- }
- });
-
- /**
- * 888b d888 888 888
- * 8888b d8888 888 888
- * 88888b.d88888 888 888
- * 888Y88888P888 .d88b. .d88888 .d88b. 888 .d8888b
- * 888 Y888P 888 d88""88b d88" 888 d8P Y8b 888 88K
- * 888 Y8P 888 888 888 888 888 88888888 888 "Y8888b.
- * 888 " 888 Y88..88P Y88b 888 Y8b. 888 X88
- * 888 888 "Y88P" "Y88888 "Y8888 888 88888P'
- */
-
- /**
- * @class OCA.WorkflowEngine.Operation
- */
- OCA.WorkflowEngine.Operation =
- OC.Backbone.Model.extend({
- defaults: {
- 'class': 'OCA\\WorkflowEngine\\Operation',
- 'name': '',
- 'checks': [],
- 'operation': ''
- }
- });
-
- /**
- * .d8888b. 888 888 888 d8b
- * d88P Y88b 888 888 888 Y8P
- * 888 888 888 888 888
- * 888 .d88b. 888 888 .d88b. .d8888b 888888 888 .d88b. 88888b. .d8888b
- * 888 d88""88b 888 888 d8P Y8b d88P" 888 888 d88""88b 888 "88b 88K
- * 888 888 888 888 888 888 88888888 888 888 888 888 888 888 888 "Y8888b.
- * Y88b d88P Y88..88P 888 888 Y8b. Y88b. Y88b. 888 Y88..88P 888 888 X88
- * "Y8888P" "Y88P" 888 888 "Y8888 "Y8888P "Y888 888 "Y88P" 888 888 88888P'
- */
-
- /**
- * @class OCA.WorkflowEngine.OperationsCollection
- *
- * collection for all configurated operations
- */
- OCA.WorkflowEngine.OperationsCollection =
- OC.Backbone.Collection.extend({
- model: OCA.WorkflowEngine.Operation,
- url: OC.generateUrl('apps/workflowengine/operations')
- });
-
- /**
- * 888 888 d8b
- * 888 888 Y8P
- * 888 888
- * Y88b d88P 888 .d88b. 888 888 888 .d8888b
- * Y88b d88P 888 d8P Y8b 888 888 888 88K
- * Y88o88P 888 88888888 888 888 888 "Y8888b.
- * Y888P 888 Y8b. Y88b 888 d88P X88
- * Y8P 888 "Y8888 "Y8888888P" 88888P'
- */
-
- /**
- * @class OCA.WorkflowEngine.OperationView
- *
- * this creates the view for a single operation
- */
- OCA.WorkflowEngine.OperationView =
- OC.Backbone.View.extend({
- templateId: '#operation-template',
- events: {
- 'change .check-class': 'checkChanged',
- 'change .check-operator': 'checkChanged',
- 'change .check-value': 'checkChanged',
- 'change .operation-name': 'operationChanged',
- 'change .operation-operation': 'operationChanged',
- 'click .button-reset': 'reset',
- 'click .button-save': 'save',
- 'click .button-add': 'add',
- 'click .button-delete': 'delete',
- 'click .button-delete-check': 'deleteCheck'
- },
- originalModel: null,
- hasChanged: false,
- message: '',
- errorMessage: '',
- saving: false,
- groups: [],
- template: function(vars) {
- return OperationTemplate(_.extend(
- {
- shortRuleDescTXT: t('workflowengine', 'Short rule description'),
- addRuleTXT: t('workflowengine', 'Add rule'),
- resetTXT: t('workflowengine', 'Reset'),
- saveTXT: t('workflowengine', 'Save'),
- savingTXT: t('workflowengine', 'Saving…')
- },
- vars
- ));
- },
- initialize: function() {
- // this creates a new copy of the object to definitely have a new reference and being able to reset the model
- this.originalModel = JSON.parse(JSON.stringify(this.model));
- this.model.on('change', function() {
- console.log('model changed');
- this.hasChanged = true;
- this.render();
- }, this);
-
- if (this.model.get('id') === undefined) {
- this.hasChanged = true;
- }
- var self = this;
- $.ajax({
- url: OC.linkToOCS('cloud/groups', 2) + 'details',
- dataType: 'json',
- quietMillis: 100,
- }).success(function(data) {
- if (data.ocs.data.groups && data.ocs.data.groups.length > 0) {
-
- data.ocs.data.groups.forEach(function(group) {
- self.groups.push({ id: group.id, displayname: group.displayname });
- });
- self.render();
-
- } else {
- OC.Notification.error(t('workflowengine', 'Group list is empty'), { type: 'error' });
- console.log(data);
- }
- }).error(function(data) {
- OC.Notification.error(t('workflowengine', 'Unable to retrieve the group list'), { type: 'error' });
- console.log(data);
- });
- },
- delete: function() {
- if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
- OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.delete, this));
- return;
- }
-
- this.model.destroy();
- this.remove();
- },
- reset: function() {
- this.hasChanged = false;
- // silent is need to not trigger the change event which resets the hasChanged attribute
- this.model.set(this.originalModel, { silent: true });
- this.render();
- },
- save: function() {
- if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
- OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.save, this));
- return;
- }
-
- var success = function(model, response, options) {
- this.saving = false;
- this.originalModel = JSON.parse(JSON.stringify(this.model));
-
- this.message = t('workflowengine', 'Saved');
- this.errorMessage = '';
- this.render();
- };
- var error = function(model, response, options) {
- this.saving = false;
- this.hasChanged = true;
-
- this.message = t('workflowengine', 'Saving failed:');
- this.errorMessage = response.responseText;
- this.render();
- };
- this.hasChanged = false;
- this.saving = true;
- this.render();
- this.model.save(null, { success: success, error: error, context: this });
- },
- add: function() {
- var checks = _.clone(this.model.get('checks')),
- classname = OCA.WorkflowEngine.availableChecks[0]['class'],
- operators = OCA.WorkflowEngine.availableChecks[0]['operators'];
-
- checks.push({
- 'class': classname,
- 'operator': operators[0]['operator'],
- 'value': ''
- });
- this.model.set({ 'checks': checks });
- },
- checkChanged: function(event) {
- var value = event.target.value,
- id = $(event.target.parentElement).data('id'),
- // this creates a new copy of the object to definitely have a new reference
- checks = JSON.parse(JSON.stringify(this.model.get('checks'))),
- key = null;
-
- for (var i = 0; i < event.target.classList.length; i++) {
- var className = event.target.classList[i];
- if (className.substr(0, 'check-'.length) === 'check-') {
- key = className.substr('check-'.length);
- break;
- }
- }
-
- if (key === null) {
- console.warn('checkChanged triggered but element doesn\'t have any "check-" class');
- return;
- }
-
- if (!_.has(checks[id], key)) {
- console.warn('key "' + key + '" is not available in check', check);
- return;
- }
-
- checks[id][key] = value;
- // if the class is changed most likely also the operators have changed
- // with this we set the operator to the first possible operator
- if (key === 'class') {
- var check = OCA.WorkflowEngine.getCheckByClass(value);
- if (!_.isUndefined(check)) {
- checks[id]['operator'] = check['operators'][0]['operator'];
- checks[id]['value'] = '';
- }
- }
- // model change will trigger render
- this.model.set({ 'checks': checks });
- },
- deleteCheck: function(event) {
- console.log(arguments);
- var id = $(event.target.parentElement).data('id'),
- checks = JSON.parse(JSON.stringify(this.model.get('checks')));
-
- // splice removes 1 element at index `id`
- checks.splice(id, 1);
- // model change will trigger render
- this.model.set({ 'checks': checks });
- },
- operationChanged: function(event) {
- var value = event.target.value,
- key = null;
-
- for (var i = 0; i < event.target.classList.length; i++) {
- var className = event.target.classList[i];
- if (className.substr(0, 'operation-'.length) === 'operation-') {
- key = className.substr('operation-'.length);
- break;
- }
- }
-
- if (key === null) {
- console.warn('operationChanged triggered but element doesn\'t have any "operation-" class');
- return;
- }
-
- if (key !== 'name' && key !== 'operation') {
- console.warn('key "' + key + '" is no valid attribute');
- return;
- }
-
- // model change will trigger render
- this.model.set(key, value);
- },
- render: function() {
- this.$el.html(this.template({
- operation: this.model.toJSON(),
- classes: OCA.WorkflowEngine.availableChecks,
- hasChanged: this.hasChanged,
- message: this.message,
- errorMessage: this.errorMessage,
- saving: this.saving
- }));
-
- var checks = this.model.get('checks');
- _.each(this.$el.find('.check'), function(element) {
- var $element = $(element),
- id = $element.data('id'),
- check = checks[id],
- valueElement = $element.find('.check-value').first();
- var self = this;
-
- _.each(OCA.WorkflowEngine.availablePlugins, function(plugin) {
- if (_.isFunction(plugin.render)) {
- plugin.render(valueElement, check, self.groups);
- }
- });
- }, this);
-
- if (this.message !== '') {
- // hide success messages after some time
- _.delay(function(elements) {
- $(elements).css('opacity', 0);
- }, 7000, this.$el.find('.msg.success'));
- this.message = '';
- }
-
- return this.$el;
- }
- });
-
- /**
- * @class OCA.WorkflowEngine.OperationsView
- *
- * this creates the view for configured operations
- */
- OCA.WorkflowEngine.OperationsView =
- OC.Backbone.View.extend({
- templateId: '#operations-template',
- collection: null,
- $el: null,
- events: {
- 'click .button-add-operation': 'add'
- },
- template: function(vars) {
- return OperationsTemplate(_.extend(
- {
- addRuleGroupTXT: t('workflowengine', 'Add rule group')
- },
- vars
- ));
- },
- initialize: function(classname) {
- if (!OCA.WorkflowEngine.availablePlugins.length) {
- OCA.WorkflowEngine.availablePlugins = OC.Plugins.getPlugins('OCA.WorkflowEngine.CheckPlugins');
- _.each(OCA.WorkflowEngine.availablePlugins, function(plugin) {
- if (_.isFunction(plugin.getCheck)) {
- OCA.WorkflowEngine.availableChecks.push(plugin.getCheck(classname));
- }
- });
- }
-
- this.collection.fetch({
- data: {
- 'class': classname
- }
- });
- this.collection.once('sync', this.render, this);
- },
- add: function() {
- var operation = this.collection.create();
- this.renderOperation(operation);
- },
- renderOperation: function(subView) {
- var operationsElement = this.$el.find('.operations');
- operationsElement.append(subView.$el);
- subView.render();
- },
- render: function() {
- this.$el.html(this.template());
- this.collection.each(this.renderOperation, this);
- }
- });
-})();
diff --git a/apps/workflowengine/src/components/Check.vue b/apps/workflowengine/src/components/Check.vue
new file mode 100644
index 00000000000..06667b1a7ee
--- /dev/null
+++ b/apps/workflowengine/src/components/Check.vue
@@ -0,0 +1,154 @@
+<template>
+ <div v-click-outside="hideDelete" class="check" @click="showDelete">
+ <Multiselect ref="checkSelector" v-model="currentOption" :options="options"
+ label="name" track-by="class" :allow-empty="false"
+ :placeholder="t('workflowengine', 'Select a filter')" @input="updateCheck" />
+ <Multiselect v-model="currentOperator" :disabled="!currentOption" :options="operators"
+ label="name" track-by="operator" :allow-empty="false"
+ :placeholder="t('workflowengine', 'Select a comparator')" @input="updateCheck" />
+ <component :is="currentOption.component" v-if="currentOperator && currentComponent" v-model="check.value"
+ :disabled="!currentOption" :check="check"
+ @input="updateCheck"
+ @valid="(valid=true) && validate()"
+ @invalid="(valid=false) && validate()" />
+ <input v-else v-model="check.value" type="text"
+ :class="{ invalid: !valid }"
+ :disabled="!currentOption" :placeholder="valuePlaceholder" @input="updateCheck">
+ <Actions v-if="deleteVisible || !currentOption">
+ <ActionButton icon="icon-delete" @click="$emit('remove')" />
+ </Actions>
+ </div>
+</template>
+
+<script>
+import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
+import { Actions } from 'nextcloud-vue/dist/Components/Actions'
+import { ActionButton } from 'nextcloud-vue/dist/Components/ActionButton'
+import ClickOutside from 'vue-click-outside'
+
+export default {
+ name: 'Check',
+ components: {
+ ActionButton,
+ Actions,
+ Multiselect
+ },
+ directives: {
+ ClickOutside
+ },
+ props: {
+ check: {
+ type: Object,
+ required: true
+ },
+ rule: {
+ type: Object,
+ required: true
+ }
+ },
+ data() {
+ return {
+ deleteVisible: false,
+ currentOption: null,
+ currentOperator: null,
+ options: [],
+ valid: true
+ }
+ },
+ computed: {
+ Checks() {
+ return this.$store.getters.getChecksForEntity(this.rule.entity)
+ },
+ operators() {
+ if (!this.currentOption) { return [] }
+ return this.Checks[this.currentOption.class].operators
+ },
+ currentComponent() {
+ if (!this.currentOption) { return [] }
+ const currentComponent = this.Checks[this.currentOption.class].component
+ return currentComponent
+ },
+ valuePlaceholder() {
+ if (this.currentOption && this.currentOption.placeholder) {
+ return this.currentOption.placeholder(this.check)
+ }
+ return ''
+ }
+ },
+ watch: {
+ 'check.operator': function() {
+ this.validate()
+ }
+ },
+ mounted() {
+ this.options = Object.values(this.Checks)
+ this.currentOption = this.Checks[this.check.class]
+ this.currentOperator = this.operators.find((operator) => operator.operator === this.check.operator)
+ },
+ methods: {
+ showDelete() {
+ this.deleteVisible = true
+ },
+ hideDelete() {
+ this.deleteVisible = false
+ },
+ validate() {
+ if (this.currentOption && this.currentOption.validate) {
+ if (this.currentOption.validate(this.check)) {
+ this.valid = true
+ } else {
+ this.valid = false
+ }
+ }
+ this.$store.dispatch('setValid', { rule: this.rule, valid: this.rule.valid && this.valid })
+ return this.valid
+ },
+ updateCheck() {
+ if (this.check.class !== this.currentOption.class) {
+ this.currentOperator = this.operators[0]
+ }
+ this.check.class = this.currentOption.class
+ this.check.operator = this.currentOperator.operator
+
+ if (!this.validate()) {
+ return
+ }
+ this.$emit('update', this.check)
+ }
+ }
+}
+</script>
+
+<style scoped lang="scss">
+ .check {
+ display: flex;
+ flex-wrap: wrap;
+ width: 100%;
+ padding-right: 20px;
+ & > *:not(.icon-delete) {
+ width: 180px;
+ }
+ & > .multiselect,
+ & > input[type=text] {
+ margin-right: 5px;
+ margin-bottom: 5px;
+ }
+ }
+ input[type=text] {
+ margin: 0;
+ }
+ ::placeholder {
+ font-size: 10px;
+ }
+ .icon-delete {
+ margin-top: -5px;
+ margin-bottom: -5px;
+ }
+ button.action-item.action-item--single.icon-delete {
+ height: 34px;
+ width: 34px;
+ }
+ .invalid {
+ border: 1px solid var(--color-error) !important;
+ }
+</style>
diff --git a/apps/workflowengine/src/components/Checks/FileMimeType.vue b/apps/workflowengine/src/components/Checks/FileMimeType.vue
new file mode 100644
index 00000000000..e99bf679f00
--- /dev/null
+++ b/apps/workflowengine/src/components/Checks/FileMimeType.vue
@@ -0,0 +1,108 @@
+<template>
+ <div>
+ <Multiselect
+ :value="currentValue"
+ :placeholder="t('workflowengine', 'Select a file type')"
+ label="label"
+ track-by="pattern"
+ :options="options" :multiple="false" :tagging="false"
+ @input="setValue">
+ <template slot="singleLabel" slot-scope="props">
+ <span class="option__icon" :class="props.option.icon" />
+ <span class="option__title option__title_single">{{ props.option.label }}</span>
+ </template>
+ <template slot="option" slot-scope="props">
+ <span class="option__icon" :class="props.option.icon" />
+ <span class="option__title">{{ props.option.label }}</span>
+ </template>
+ </Multiselect>
+ <input v-if="!isPredefined" type="text" :value="currentValue.pattern"
+ @input="updateCustom">
+ </div>
+</template>
+
+<script>
+import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
+import valueMixin from './../../mixins/valueMixin'
+
+export default {
+ name: 'FileMimeType',
+ components: {
+ Multiselect
+ },
+ mixins: [
+ valueMixin
+ ],
+ data() {
+ return {
+ predefinedTypes: [
+ {
+ icon: 'icon-picture',
+ label: t('workflowengine', 'Images'),
+ pattern: '/image\\/.*/'
+ },
+ {
+ icon: 'icon-category-office',
+ label: t('workflowengine', 'Office documents'),
+ pattern: '/(vnd\\.(ms-|openxmlformats-).*))$/'
+ },
+ {
+ icon: 'icon-filetype-file',
+ label: t('workflowengine', 'PDF documents'),
+ pattern: 'application/pdf'
+ }
+ ]
+ }
+ },
+ computed: {
+ options() {
+ return [...this.predefinedTypes, this.customValue]
+ },
+ isPredefined() {
+ const matchingPredefined = this.predefinedTypes.find((type) => this.newValue === type.pattern)
+ if (matchingPredefined) {
+ return true
+ }
+ return false
+ },
+ customValue() {
+ return {
+ icon: 'icon-settings-dark',
+ label: t('workflowengine', 'Custom mimetype'),
+ pattern: ''
+ }
+ },
+ currentValue() {
+ const matchingPredefined = this.predefinedTypes.find((type) => this.newValue === type.pattern)
+ if (matchingPredefined) {
+ return matchingPredefined
+ }
+ return {
+ icon: 'icon-settings-dark',
+ label: t('workflowengine', 'Custom mimetype'),
+ pattern: this.newValue
+ }
+ }
+ },
+ methods: {
+ validateRegex(string) {
+ var regexRegex = /^\/(.*)\/([gui]{0,3})$/
+ var result = regexRegex.exec(string)
+ return result !== null
+ },
+ setValue(value) {
+ // TODO: check if value requires a regex and set the check operator according to that
+ if (value !== null) {
+ this.newValue = value.pattern
+ this.$emit('input', this.newValue)
+ }
+ },
+ updateCustom(event) {
+ this.newValue = event.target.value
+ this.$emit('input', this.newValue)
+ }
+ }
+}
+</script>
+
+<style scoped src="./../../css/multiselect.css"></style>
diff --git a/apps/workflowengine/src/components/Checks/FileSystemTag.vue b/apps/workflowengine/src/components/Checks/FileSystemTag.vue
new file mode 100644
index 00000000000..e2f66b30a4b
--- /dev/null
+++ b/apps/workflowengine/src/components/Checks/FileSystemTag.vue
@@ -0,0 +1,73 @@
+<!--
+ - @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ -
+ - @author Julius Härtl <jus@bitgrid.net>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
+
+<template>
+ <MultiselectTag v-model="newValue" :multiple="false"
+ label="Select a tag"
+ @input="update" />
+</template>
+
+<script>
+import { MultiselectTag } from './MultiselectTag'
+
+export default {
+ name: 'FileSystemTag',
+ components: {
+ MultiselectTag
+ },
+ props: {
+ value: {
+ type: String,
+ default: ''
+ }
+ },
+ data() {
+ return {
+ newValue: []
+ }
+ },
+ watch: {
+ value() {
+ this.updateValue()
+ }
+ },
+ beforeMount() {
+ this.updateValue()
+ },
+ methods: {
+ updateValue() {
+ if (this.value !== '') {
+ this.newValue = this.value
+ } else {
+ this.newValue = null
+ }
+ },
+ update() {
+ this.$emit('input', this.newValue || '')
+ }
+ }
+}
+</script>
+
+<style scoped>
+
+</style>
diff --git a/apps/workflowengine/src/components/Checks/MultiselectTag/MultiselectTag.vue b/apps/workflowengine/src/components/Checks/MultiselectTag/MultiselectTag.vue
new file mode 100644
index 00000000000..88b56a1d4e9
--- /dev/null
+++ b/apps/workflowengine/src/components/Checks/MultiselectTag/MultiselectTag.vue
@@ -0,0 +1,127 @@
+<!--
+ - @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ -
+ - @author Julius Härtl <jus@bitgrid.net>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
+
+<template>
+ <Multiselect v-model="inputValObjects"
+ :options="tags" :options-limit="5"
+ :placeholder="label"
+ track-by="id"
+ :custom-label="tagLabel"
+ class="multiselect-vue" :multiple="multiple"
+ :close-on-select="false" :tag-width="60"
+ :disabled="disabled" @input="update">
+ <span slot="noResult">{{ t('core', 'No results') }}</span>
+ <template #option="scope">
+ {{ tagLabel(scope.option) }}
+ </template>
+ </multiselect>
+</template>
+
+<script>
+import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
+import { searchTags } from './api'
+
+let uuid = 0
+export default {
+ name: 'MultiselectTag',
+ components: {
+ Multiselect
+ },
+ props: {
+ label: {
+ type: String,
+ required: true
+ },
+ value: {
+ default() {
+ return []
+ }
+ },
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ multiple: {
+ type: Boolean,
+ default: true
+ }
+ },
+ data() {
+ return {
+ inputValObjects: [],
+ tags: []
+ }
+ },
+ computed: {
+ id() {
+ return 'settings-input-text-' + this.uuid
+ }
+ },
+ watch: {
+ value(newVal) {
+ this.inputValObjects = this.getValueObject()
+ }
+ },
+ beforeCreate: function() {
+ this.uuid = uuid.toString()
+ uuid += 1
+ searchTags().then((result) => {
+ this.tags = result
+ this.inputValObjects = this.getValueObject()
+ }).catch(console.error.bind(this))
+ },
+ methods: {
+ getValueObject() {
+ if (this.tags.length === 0) {
+ return []
+ }
+ if (this.multiple) {
+ return this.value.filter((tag) => tag !== '').map(
+ (id) => this.tags.find((tag2) => tag2.id === id)
+ )
+ } else {
+ return this.tags.find((tag) => tag.id === this.value)
+ }
+ },
+ update() {
+ if (this.multiple) {
+ this.$emit('input', this.inputValObjects.map((element) => element.id))
+ } else {
+ if (this.inputValObjects === null) {
+ this.$emit('input', '')
+ } else {
+ this.$emit('input', this.inputValObjects.id)
+ }
+ }
+ },
+ tagLabel({ displayName, userVisible, userAssignable }) {
+ if (userVisible === false) {
+ return t('systemtags', '%s (invisible)').replace('%s', displayName)
+ }
+ if (userAssignable === false) {
+ return t('systemtags', '%s (restricted)').replace('%s', displayName)
+ }
+ return displayName
+ }
+ }
+}
+</script>
diff --git a/apps/workflowengine/src/components/Checks/MultiselectTag/api.js b/apps/workflowengine/src/components/Checks/MultiselectTag/api.js
new file mode 100644
index 00000000000..bdbab2b974f
--- /dev/null
+++ b/apps/workflowengine/src/components/Checks/MultiselectTag/api.js
@@ -0,0 +1,90 @@
+import axios from 'nextcloud-axios'
+import { generateRemoteUrl } from 'nextcloud-router'
+
+const xmlToJson = (xml) => {
+ let obj = {}
+
+ if (xml.nodeType === 1) {
+ if (xml.attributes.length > 0) {
+ obj['@attributes'] = {}
+ for (let j = 0; j < xml.attributes.length; j++) {
+ const attribute = xml.attributes.item(j)
+ obj['@attributes'][attribute.nodeName] = attribute.nodeValue
+ }
+ }
+ } else if (xml.nodeType === 3) {
+ obj = xml.nodeValue
+ }
+
+ if (xml.hasChildNodes()) {
+ for (let i = 0; i < xml.childNodes.length; i++) {
+ const item = xml.childNodes.item(i)
+ const nodeName = item.nodeName
+ if (typeof (obj[nodeName]) === 'undefined') {
+ obj[nodeName] = xmlToJson(item)
+ } else {
+ if (typeof obj[nodeName].push === 'undefined') {
+ var old = obj[nodeName]
+ obj[nodeName] = []
+ obj[nodeName].push(old)
+ }
+ obj[nodeName].push(xmlToJson(item))
+ }
+ }
+ }
+ return obj
+}
+
+const parseXml = (xml) => {
+ let dom = null
+ try {
+ dom = (new DOMParser()).parseFromString(xml, 'text/xml')
+ } catch (e) {
+ console.error('Failed to parse xml document', e)
+ }
+ return dom
+}
+
+const xmlToTagList = (xml) => {
+ const json = xmlToJson(parseXml(xml))
+ const list = json['d:multistatus']['d:response']
+ const result = []
+ for (const index in list) {
+ const tag = list[index]['d:propstat']
+
+ if (tag['d:status']['#text'] !== 'HTTP/1.1 200 OK') {
+ continue
+ }
+ result.push({
+ id: tag['d:prop']['oc:id']['#text'],
+ displayName: tag['d:prop']['oc:display-name']['#text'],
+ canAssign: tag['d:prop']['oc:can-assign']['#text'] === 'true',
+ userAssignable: tag['d:prop']['oc:user-assignable']['#text'] === 'true',
+ userVisible: tag['d:prop']['oc:user-visible']['#text'] === 'true'
+ })
+ }
+ return result
+}
+
+const searchTags = function() {
+ return axios({
+ method: 'PROPFIND',
+ url: generateRemoteUrl('dav') + '/systemtags/',
+ data: `<?xml version="1.0"?>
+ <d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
+ <d:prop>
+ <oc:id />
+ <oc:display-name />
+ <oc:user-visible />
+ <oc:user-assignable />
+ <oc:can-assign />
+ </d:prop>
+ </d:propfind>`
+ }).then((response) => {
+ return xmlToTagList(response.data)
+ })
+}
+
+export {
+ searchTags
+}
diff --git a/apps/workflowengine/src/components/Checks/MultiselectTag/index.js b/apps/workflowengine/src/components/Checks/MultiselectTag/index.js
new file mode 100644
index 00000000000..69b7e277e76
--- /dev/null
+++ b/apps/workflowengine/src/components/Checks/MultiselectTag/index.js
@@ -0,0 +1,4 @@
+import MultiselectTag from './MultiselectTag'
+
+export default MultiselectTag
+export { MultiselectTag }
diff --git a/apps/workflowengine/src/components/Checks/RequestTime.vue b/apps/workflowengine/src/components/Checks/RequestTime.vue
new file mode 100644
index 00000000000..ce306c0541e
--- /dev/null
+++ b/apps/workflowengine/src/components/Checks/RequestTime.vue
@@ -0,0 +1,94 @@
+<template>
+ <div class="timeslot">
+ <Multiselect v-model="newValue.timezone" :options="timezones" @input="update" />
+ <input v-model="newValue.startTime" type="text" class="timeslot--start"
+ placeholder="08:00" @input="update">
+ <input v-model="newValue.endTime" type="text" placeholder="18:00"
+ @input="update">
+ </div>
+</template>
+
+<script>
+import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
+import moment from 'moment-timezone'
+import valueMixin from '../../mixins/valueMixin'
+
+const zones = moment.tz.names()
+export default {
+ name: 'RequestTime',
+ components: {
+ Multiselect
+ },
+ mixins: [
+ valueMixin
+ ],
+ props: {
+ value: {
+ type: String,
+ default: '1 MB'
+ }
+ },
+ data() {
+ return {
+ timezones: zones,
+ valid: false,
+ newValue: {
+ startTime: null,
+ endTime: null,
+ timezone: moment.tz.guess()
+ }
+ }
+ },
+ methods: {
+ updateInternalValue(value) {
+ var data = JSON.parse(value)
+ var startTime = data[0].split(' ', 2)[0]
+ var endTime = data[1].split(' ', 2)[0]
+ var timezone = data[0].split(' ', 2)[1]
+ this.newValue = {
+ startTime: startTime,
+ endTime: endTime,
+ timezone: timezone
+ }
+ },
+ validate() {
+ return this.newValue.startTime && this.newValue.startTime.match(/^(0[0-9]|1[0-9]|2[0-3]|[0-9]):[0-5][0-9]$/i) !== null
+ && this.newValue.endTime && this.newValue.endTime.match(/^(0[0-9]|1[0-9]|2[0-3]|[0-9]):[0-5][0-9]$/i) !== null
+ && moment.tz.zone(this.newValue.timezone) !== null
+ },
+ update() {
+ if (this.validate()) {
+ const output = `["${this.newValue.startTime} ${this.newValue.timezone}","${this.newValue.endTime} ${this.newValue.timezone}"]`
+ this.$emit('input', output)
+ this.valid = true
+ } else {
+ this.valid = false
+ }
+ }
+ }
+}
+</script>
+
+<style scoped lang="scss">
+ .timeslot {
+ display: flex;
+ flex-grow: 1;
+ flex-wrap: wrap;
+ max-width: 180px;
+
+ .multiselect {
+ width: 100%;
+ margin-bottom: 5px;
+ }
+
+ input[type=text] {
+ width: 50%;
+ margin: 0;
+ margin-bottom: 5px;
+ &.timeslot--start {
+ margin-right: 5px;
+ width: calc(50% - 5px);
+ }
+ }
+ }
+</style>
diff --git a/apps/workflowengine/src/components/Checks/RequestURL.vue b/apps/workflowengine/src/components/Checks/RequestURL.vue
new file mode 100644
index 00000000000..2ddba526d1e
--- /dev/null
+++ b/apps/workflowengine/src/components/Checks/RequestURL.vue
@@ -0,0 +1,137 @@
+<!--
+ - @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ -
+ - @author Julius Härtl <jus@bitgrid.net>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
+
+<template>
+ <div>
+ <Multiselect
+ :value="currentValue"
+ :placeholder="t('workflowengine', 'Select a request URL')"
+ label="label"
+ track-by="pattern"
+ group-values="children"
+ group-label="label"
+ :options="options" :multiple="false" :tagging="false"
+ @input="setValue">
+ <template slot="singleLabel" slot-scope="props">
+ <span class="option__icon" :class="props.option.icon" />
+ <span class="option__title option__title_single">{{ props.option.label }}</span>
+ </template>
+ <template slot="option" slot-scope="props">
+ <span class="option__icon" :class="props.option.icon" />
+ <span class="option__title">{{ props.option.label }} {{ props.option.$groupLabel }}</span>
+ </template>
+ </Multiselect>
+ <input v-if="!isPredefined" type="text"
+ :value="currentValue.pattern"
+ :placeholder="placeholder" @input="updateCustom">
+ </div>
+</template>
+
+<script>
+import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
+import valueMixin from '../../mixins/valueMixin'
+
+export default {
+ name: 'RequestURL',
+ components: {
+ Multiselect
+ },
+ mixins: [
+ valueMixin
+ ],
+ data() {
+ return {
+ newValue: '',
+ predefinedTypes: [
+ {
+ label: t('workflowengine', 'Predefined URLs'),
+ children: [
+ { pattern: 'webdav', label: t('workflowengine', 'Files WebDAV') }
+ ]
+ }
+ ]
+ }
+ },
+ computed: {
+ options() {
+ return [...this.predefinedTypes, this.customValue]
+ },
+ placeholder() {
+ if (this.check.operator === 'matches' || this.check.operator === '!matches') {
+ return '/^https\\:\\/\\/localhost\\/index\\.php$/i'
+ }
+ return 'https://localhost/index.php'
+ },
+ matchingPredefined() {
+ return this.predefinedTypes
+ .map(groups => groups.children)
+ .flat()
+ .find((type) => this.newValue === type.pattern)
+ },
+ isPredefined() {
+ return !!this.matchingPredefined
+ },
+ customValue() {
+ return {
+ label: t('workflowengine', 'Others'),
+ children: [
+ {
+ icon: 'icon-settings-dark',
+ label: t('workflowengine', 'Custom URL'),
+ pattern: ''
+ }
+ ]
+ }
+ },
+ currentValue() {
+ if (this.matchingPredefined) {
+ return this.matchingPredefined
+ }
+ return {
+ icon: 'icon-settings-dark',
+ label: t('workflowengine', 'Custom URL'),
+ pattern: this.newValue
+ }
+ }
+ },
+ methods: {
+ validateRegex(string) {
+ var regexRegex = /^\/(.*)\/([gui]{0,3})$/
+ var result = regexRegex.exec(string)
+ return result !== null
+ },
+ setValue(value) {
+ // TODO: check if value requires a regex and set the check operator according to that
+ if (value !== null) {
+ this.newValue = value.pattern
+ this.$emit('input', this.newValue)
+ }
+ },
+ updateCustom(event) {
+ this.newValue = event.target.value
+ this.$emit('input', this.newValue)
+ }
+ }
+}
+</script>
+
+<style scoped src="./../../css/multiselect.css"></style>
diff --git a/apps/workflowengine/src/components/Checks/RequestUserAgent.vue b/apps/workflowengine/src/components/Checks/RequestUserAgent.vue
new file mode 100644
index 00000000000..e80071ab9fa
--- /dev/null
+++ b/apps/workflowengine/src/components/Checks/RequestUserAgent.vue
@@ -0,0 +1,133 @@
+<!--
+ - @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ -
+ - @author Julius Härtl <jus@bitgrid.net>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
+
+<template>
+ <div>
+ <Multiselect
+ :value="currentValue"
+ :placeholder="t('workflowengine', 'Select a user agent')"
+ label="label"
+ track-by="pattern"
+ group-values="children"
+ group-label="label"
+ :options="options" :multiple="false" :tagging="false"
+ @input="setValue">
+ <template slot="singleLabel" slot-scope="props">
+ <span class="option__icon" :class="props.option.icon" />
+ <span class="option__title option__title_single">{{ props.option.label }}</span>
+ </template>
+ <template slot="option" slot-scope="props">
+ <span class="option__icon" :class="props.option.icon" />
+ <span class="option__title">{{ props.option.label }} {{ props.option.$groupLabel }}</span>
+ </template>
+ </Multiselect>
+ <input v-if="!isPredefined" type="text" :value="currentValue.pattern"
+ @input="updateCustom">
+ </div>
+</template>
+
+<script>
+import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
+import valueMixin from '../../mixins/valueMixin'
+
+export default {
+ name: 'RequestUserAgent',
+ components: {
+ Multiselect
+ },
+ mixins: [
+ valueMixin
+ ],
+ data() {
+ return {
+ newValue: '',
+ predefinedTypes: [
+ {
+ label: t('workflowengine', 'Sync clients'),
+ children: [
+ { pattern: 'android', label: t('workflowengine', 'Android client'), icon: 'icon-phone' },
+ { pattern: 'ios', label: t('workflowengine', 'iOS client'), icon: 'icon-phone' },
+ { pattern: 'desktop', label: t('workflowengine', 'Desktop client'), icon: 'icon-desktop' },
+ { pattern: 'mail', label: t('workflowengine', 'Thunderbird & Outlook addons'), icon: 'icon-mail' }
+ ]
+ }
+ ]
+ }
+ },
+ computed: {
+ options() {
+ return [...this.predefinedTypes, this.customValue]
+ },
+ matchingPredefined() {
+ return this.predefinedTypes
+ .map(groups => groups.children)
+ .flat()
+ .find((type) => this.newValue === type.pattern)
+ },
+ isPredefined() {
+ return !!this.matchingPredefined
+ },
+ customValue() {
+ return {
+ label: t('workflowengine', 'Others'),
+ children: [
+ {
+ icon: 'icon-settings-dark',
+ label: t('workflowengine', 'Custom user agent'),
+ pattern: ''
+ }
+ ]
+ }
+ },
+ currentValue() {
+ if (this.matchingPredefined) {
+ return this.matchingPredefined
+ }
+ return {
+ icon: 'icon-settings-dark',
+ label: t('workflowengine', 'Custom user agent'),
+ pattern: this.newValue
+ }
+ }
+ },
+ methods: {
+ validateRegex(string) {
+ var regexRegex = /^\/(.*)\/([gui]{0,3})$/
+ var result = regexRegex.exec(string)
+ return result !== null
+ },
+ setValue(value) {
+ // TODO: check if value requires a regex and set the check operator according to that
+ if (value !== null) {
+ this.newValue = value.pattern
+ this.$emit('input', this.newValue)
+ }
+ },
+ updateCustom(event) {
+ this.newValue = event.target.value
+ this.$emit('input', this.newValue)
+ }
+ }
+}
+</script>
+
+<style scoped src="./../../css/multiselect.css"></style>
diff --git a/apps/workflowengine/src/components/Checks/RequestUserGroup.vue b/apps/workflowengine/src/components/Checks/RequestUserGroup.vue
new file mode 100644
index 00000000000..843bbf127e0
--- /dev/null
+++ b/apps/workflowengine/src/components/Checks/RequestUserGroup.vue
@@ -0,0 +1,77 @@
+<!--
+ - @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ -
+ - @author Julius Härtl <jus@bitgrid.net>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
+
+<template>
+ <div>
+ <Multiselect v-model="newValue"
+ :class="{'icon-loading-small': groups.length === 0}" :options="groups"
+ :multiple="false"
+ label="displayname" track-by="id"
+ @input="setValue" />
+ </div>
+</template>
+
+<script>
+import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
+import valueMixin from '../../mixins/valueMixin'
+import axios from 'nextcloud-axios'
+export default {
+ name: 'RequestUserGroup',
+ components: {
+ Multiselect
+ },
+ mixins: [
+ valueMixin
+ ],
+ data() {
+ return {
+ groups: []
+ }
+ },
+ beforeMount() {
+ axios.get(OC.linkToOCS('cloud', 2) + 'groups').then((response) => {
+ this.groups = response.data.ocs.data.groups.reduce((obj, item) => {
+ obj.push({
+ id: item,
+ displayname: item
+ })
+ return obj
+ }, [])
+ this.updateInternalValue(this.value)
+ }, (error) => {
+ console.error('Error while loading group list', error.response)
+ })
+ },
+ methods: {
+ updateInternalValue() {
+ this.newValue = this.groups.find(group => group.id === this.value) || null
+ },
+ setValue(value) {
+ if (value !== null) {
+ this.$emit('input', this.newValue.id)
+ }
+ }
+ }
+}
+</script>
+
+<style scoped src="./../../css/multiselect.css"></style>
diff --git a/apps/workflowengine/src/components/Checks/file.js b/apps/workflowengine/src/components/Checks/file.js
new file mode 100644
index 00000000000..ce6887316c2
--- /dev/null
+++ b/apps/workflowengine/src/components/Checks/file.js
@@ -0,0 +1,105 @@
+/*
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import { stringValidator, validateIPv4, validateIPv6 } from './../../helpers/validators'
+import FileMimeType from './FileMimeType'
+import FileSystemTag from './FileSystemTag'
+
+const FileChecks = [
+ {
+ class: 'OCA\\WorkflowEngine\\Check\\FileName',
+ name: t('workflowengine', 'File name'),
+ operators: [
+ { operator: 'is', name: t('workflowengine', 'is') },
+ { operator: '!is', name: t('workflowengine', 'is not') },
+ { operator: 'matches', name: t('workflowengine', 'matches') },
+ { operator: '!matches', name: t('workflowengine', 'does not match') }
+ ],
+ placeholder: (check) => {
+ if (check.operator === 'matches' || check.operator === '!matches') {
+ return '/^dummy-.+$/i'
+ }
+ return 'filename.txt'
+ },
+ validate: stringValidator
+ },
+
+ {
+ class: 'OCA\\WorkflowEngine\\Check\\FileMimeType',
+ name: t('workflowengine', 'File MIME type'),
+ operators: [
+ { operator: 'is', name: t('workflowengine', 'is') },
+ { operator: '!is', name: t('workflowengine', 'is not') },
+ { operator: 'matches', name: t('workflowengine', 'matches') },
+ { operator: '!matches', name: t('workflowengine', 'does not match') }
+ ],
+ component: FileMimeType
+ },
+
+ {
+ class: 'OCA\\WorkflowEngine\\Check\\FileSize',
+ name: t('workflowengine', 'File size (upload)'),
+ operators: [
+ { operator: 'less', name: t('workflowengine', 'less') },
+ { operator: '!greater', name: t('workflowengine', 'less or equals') },
+ { operator: '!less', name: t('workflowengine', 'greater or equals') },
+ { operator: 'greater', name: t('workflowengine', 'greater') }
+ ],
+ placeholder: (check) => '5 MB',
+ validate: (check) => check.value.match(/^[0-9]+[ ]?[kmgt]?b$/i) !== null
+ },
+
+ {
+ class: 'OCA\\WorkflowEngine\\Check\\RequestRemoteAddress',
+ name: t('workflowengine', 'Request remote address'),
+ operators: [
+ { operator: 'matchesIPv4', name: t('workflowengine', 'matches IPv4') },
+ { operator: '!matchesIPv4', name: t('workflowengine', 'does not match IPv4') },
+ { operator: 'matchesIPv6', name: t('workflowengine', 'matches IPv6') },
+ { operator: '!matchesIPv6', name: t('workflowengine', 'does not match IPv6') }
+ ],
+ placeholder: (check) => {
+ if (check.operator === 'matchesIPv6' || check.operator === '!matchesIPv6') {
+ return '::1/128'
+ }
+ return '127.0.0.1/32'
+ },
+ validate: (check) => {
+ if (check.operator === 'matchesIPv6' || check.operator === '!matchesIPv6') {
+ return validateIPv6(check.value)
+ }
+ return validateIPv4(check.value)
+ }
+ },
+
+ {
+ class: 'OCA\\WorkflowEngine\\Check\\FileSystemTags',
+ name: t('workflowengine', 'File system tag'),
+ operators: [
+ { operator: 'is', name: t('workflowengine', 'is tagged with') },
+ { operator: '!is', name: t('workflowengine', 'is not tagged with') }
+ ],
+ component: FileSystemTag
+ }
+]
+
+export default FileChecks
diff --git a/apps/workflowengine/src/components/Checks/index.js b/apps/workflowengine/src/components/Checks/index.js
new file mode 100644
index 00000000000..d20472111b5
--- /dev/null
+++ b/apps/workflowengine/src/components/Checks/index.js
@@ -0,0 +1,26 @@
+/*
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import FileChecks from './file'
+import RequestChecks from './request'
+
+export default [...FileChecks, ...RequestChecks]
diff --git a/apps/workflowengine/src/components/Checks/request.js b/apps/workflowengine/src/components/Checks/request.js
new file mode 100644
index 00000000000..1059bf45b5a
--- /dev/null
+++ b/apps/workflowengine/src/components/Checks/request.js
@@ -0,0 +1,71 @@
+/*
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import RequestUserAgent from './RequestUserAgent'
+import RequestTime from './RequestTime'
+import RequestURL from './RequestURL'
+import RequestUserGroup from './RequestUserGroup'
+
+const RequestChecks = [
+ {
+ class: 'OCA\\WorkflowEngine\\Check\\RequestURL',
+ name: t('workflowengine', 'Request URL'),
+ operators: [
+ { operator: 'is', name: t('workflowengine', 'is') },
+ { operator: '!is', name: t('workflowengine', 'is not') },
+ { operator: 'matches', name: t('workflowengine', 'matches') },
+ { operator: '!matches', name: t('workflowengine', 'does not match') }
+ ],
+ component: RequestURL
+ },
+ {
+ class: 'OCA\\WorkflowEngine\\Check\\RequestTime',
+ name: t('workflowengine', 'Request time'),
+ operators: [
+ { operator: 'in', name: t('workflowengine', 'between') },
+ { operator: '!in', name: t('workflowengine', 'not between') }
+ ],
+ component: RequestTime
+ },
+ {
+ class: 'OCA\\WorkflowEngine\\Check\\RequestUserAgent',
+ name: t('workflowengine', 'Request user agent'),
+ operators: [
+ { operator: 'is', name: t('workflowengine', 'is') },
+ { operator: '!is', name: t('workflowengine', 'is not') },
+ { operator: 'matches', name: t('workflowengine', 'matches') },
+ { operator: '!matches', name: t('workflowengine', 'does not match') }
+ ],
+ component: RequestUserAgent
+ },
+ {
+ class: 'OCA\\WorkflowEngine\\Check\\UserGroupMembership',
+ name: t('workflowengine', 'User group membership'),
+ operators: [
+ { operator: 'is', name: t('workflowengine', 'is member of') },
+ { operator: '!is', name: t('workflowengine', 'is not member of') }
+ ],
+ component: RequestUserGroup
+ }
+]
+
+export default RequestChecks
diff --git a/apps/workflowengine/src/components/Event.vue b/apps/workflowengine/src/components/Event.vue
new file mode 100644
index 00000000000..ea153758c74
--- /dev/null
+++ b/apps/workflowengine/src/components/Event.vue
@@ -0,0 +1,105 @@
+<template>
+ <div>
+ <div v-if="operation.isComplex && operation.fixedEntity !== ''" class="isComplex">
+ <img class="option__icon" :src="entity.icon">
+ <span class="option__title option__title_single">{{ operation.triggerHint }}</span>
+ </div>
+ <Multiselect v-else :value="currentEvent" :options="allEvents"
+ label="eventName" track-by="id" :allow-empty="false"
+ :disabled="allEvents.length <= 1" @input="updateEvent">
+ <template slot="singleLabel" slot-scope="props">
+ <img class="option__icon" :src="props.option.entity.icon">
+ <span class="option__title option__title_single">{{ props.option.displayName }}</span>
+ </template>
+ <template slot="option" slot-scope="props">
+ <img class="option__icon" :src="props.option.entity.icon">
+ <span class="option__title">{{ props.option.displayName }}</span>
+ </template>
+ </Multiselect>
+ </div>
+</template>
+
+<script>
+import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
+
+export default {
+ name: 'Event',
+ components: {
+ Multiselect
+ },
+ props: {
+ rule: {
+ type: Object,
+ required: true
+ }
+ },
+ computed: {
+ entity() {
+ return this.$store.getters.getEntityForOperation(this.operation)
+ },
+ operation() {
+ return this.$store.getters.getOperationForRule(this.rule)
+ },
+ allEvents() {
+ return this.$store.getters.getEventsForOperation(this.operation)
+ },
+ currentEvent() {
+ if (!this.rule.events) {
+ return this.allEvents.length > 0 ? this.allEvents[0] : null
+ }
+ return this.allEvents.find(event => event.entity.id === this.rule.entity && this.rule.events.indexOf(event.eventName) !== -1)
+ }
+ },
+ methods: {
+ updateEvent(event) {
+ this.$set(this.rule, 'entity', event.entity.id)
+ this.$set(this.rule, 'events', [event.eventName])
+ this.$store.dispatch('updateRule', this.rule)
+ }
+ }
+}
+</script>
+
+<style scoped lang="scss">
+ .isComplex {
+ img {
+ vertical-align: top;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ padding-left: 4px;
+ }
+ span {
+ padding-top: 2px;
+ display: inline-block;
+ }
+ }
+ .multiselect::v-deep .multiselect__single {
+ display: flex;
+ }
+ .multiselect:not(.multiselect--active)::v-deep .multiselect__tags {
+ background-color: var(--color-main-background) !important;
+ border: 1px solid transparent;
+ }
+
+ .multiselect::v-deep .multiselect__tags .multiselect__single {
+ background-color: var(--color-main-background) !important;
+ }
+
+ .multiselect:not(.multiselect--disabled)::v-deep .multiselect__tags .multiselect__single {
+ background-image: var(--icon-triangle-s-000);
+ background-repeat: no-repeat;
+ background-position: right center;
+ }
+
+ input {
+ border: 1px solid transparent;
+ }
+
+ .option__title {
+ margin-left: 5px;
+ color: var(--color-main-text);
+ }
+ .option__title_single {
+ font-weight: 900;
+ }
+</style>
diff --git a/apps/workflowengine/src/components/Operation.vue b/apps/workflowengine/src/components/Operation.vue
new file mode 100644
index 00000000000..ad44d376934
--- /dev/null
+++ b/apps/workflowengine/src/components/Operation.vue
@@ -0,0 +1,109 @@
+<template>
+ <div class="actions__item" :class="{'colored': colored}" :style="{ backgroundColor: colored ? operation.color : 'transparent' }">
+ <div class="icon" :class="operation.iconClass" :style="{ backgroundImage: operation.iconClass ? '' : `url(${operation.icon})` }" />
+ <div class="actions__item__description">
+ <h3>{{ operation.name }}</h3>
+ <small>{{ operation.description }}</small>
+ </div>
+ <div class="actions__item_options">
+ <slot />
+ </div>
+ </div>
+</template>
+
+<script>
+export default {
+ name: 'Operation',
+ props: {
+ operation: {
+ type: Object,
+ required: true
+ },
+ colored: {
+ type: Boolean,
+ default: true
+ }
+ }
+}
+</script>
+
+<style scoped lang="scss">
+ .actions__item {
+ display: flex;
+ flex-wrap: wrap;
+ flex-direction: column;
+ flex-grow: 1;
+ margin-left: -1px;
+ padding: 10px;
+ border-radius: var(--border-radius-large);
+ margin-right: 20px;
+ margin-bottom: 20px;
+ }
+ .icon {
+ display: block;
+ width: 100%;
+ height: 50px;
+ background-size: 50px 50px;
+ background-position: center center;
+ margin-top: 10px;
+ margin-bottom: 20px;
+ background-repeat: no-repeat;
+ }
+ .actions__item__description {
+ text-align: center;
+ }
+ .actions__item_options {
+ width: 100%;
+ margin-top: 10px;
+ }
+ h3, small {
+ padding: 6px;
+ display: block;
+ }
+ h3 {
+ margin: 0;
+ padding: 0;
+ font-weight: 500;
+ }
+ small {
+ font-size: 10pt;
+ }
+
+ .colored {
+ background-color: var(--color-primary-element);
+ * {
+ color: var(--color-primary-text)
+ }
+ }
+
+ .actions__item:not(.colored) {
+ flex-direction: row;
+
+ .actions__item__description {
+ padding-top: 5px;
+ text-align: left;
+ small {
+ padding: 0;
+ }
+ }
+ .icon {
+ width: 50px;
+ margin: 0;
+ margin-right: 10px;
+ &:not(.icon-invert) {
+ filter: invert(1);
+ }
+ }
+ }
+
+ /* TODO: those should be provided by the backend, remove once ready */
+ .icon-block {
+ background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8' standalone='no'%3F%3E%3Csvg xmlns='http://www.w3.org/2000/svg' height='32' width='32' version='1.1' viewBox='0 0 32 32'%3E%3Cpath fill='%23fff' d='m10.203 2-8.203 8.203v11.594l8.203 8.203h11.594l8.203-8.203v-11.594l-8.203-8.203h-11.594zm11.097 5.3092 3.345 3.3448-5.346 5.346 5.346 5.346-3.299 3.299-5.346-5.346-5.346 5.346-3.2992-3.299 5.3462-5.346-5.3462-5.346 3.2992-3.2992 5.346 5.3462 5.3-5.3918z'/%3E%3C/svg%3E");
+ }
+ .icon-convert-pdf {
+ background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' version='1.1' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23fff'%3E%3Cpath d='m7.0624 2.9056c-0.20526 0-0.36653 0.14989-0.36653 0.34066v8.8571c0 0.19077 0.16127 0.34066 0.36653 0.34066h8.0637c0.20526 0 0.36653-0.14989 0.36653-0.34066v-7.1538l-2.1992-2.044zm4.2518 2.6571s0.05132 0.64725-0.10996 1.567c0.52414 1.3987 1.0996 1.5875 1.3562 1.7033 0.54247-0.040873 1.1472-0.068129 1.6861 0.2044 0.36653 0.19486 0.65536 1.022-0.21992 1.022-0.39586-0.023161-1.1267-0.23574-1.6494-0.47692-0.78145 0.081762-1.752 0.21802-2.5657 0.54505-0.91633 1.4308-1.3268 1.6352-1.6494 1.6352-0.89067-0.21802-0.41052-1.3149-0.073304-1.4989 0.40319-0.32022 0.87601-0.50417 1.0263-0.54505 0.065969-0.10221 1.0146-1.8327 1.2462-2.5549-0.21992-0.69767-0.27123-1.4349-0.14661-1.8736 0.57179-0.69358 1.0996-0.23846 1.0996 0.27253zm-0.51315 2.1121c-0.19793 0.72015-0.98817 2.1012-0.95299 2.044 0.81004-0.33044 1.5394-0.42923 2.3458-0.54505-0.38559-0.16011-0.84009-0.17033-1.3928-1.4989z' stroke-width='.70672'/%3E%3Cpath d='m16.246-9.7651c-2.05e-4 0.0144-6e-3 0.027629-6e-3 0.042066-0.0044 2.2592 2.0761 3.742 4.0564 3.6477v1.2349l2.3737-2.2265-2.3377-2.3407-3e-3 1.2289c-1.0287 0.1337-1.8811-0.66867-1.8659-1.5414 2.9e-4 -0.016152 0.0083-0.029062 9e-3 -0.045071z' stroke-width='.67694'/%3E%3Cpath d='m3.2734 5.1094v1.4492h-2.7676v2.5h2.7246l-0.0019532 1.4629 3.0996-2.6387-3.0547-2.7734z'/%3E%3Cpath d='m8.334-11.356c-0.78035-0.78051-1.9205-1.0863-2.9866-0.80073a0.51533 0.51533 0 1 0 0.26293 0.99405c0.71208-0.19075 1.4702 0.01747 1.9914 0.53876 0.46076 0.46083 0.65567 1.1026 0.56688 1.7376a0.61838 0.61838 0 1 0-0.87225 0.87442l0.8687 0.86886a0.61838 0.61838 0 0 0 0.86992 7.91e-5l0.86886-0.8687a0.61838 0.61838 0 0 0 0.0011543-0.88702 0.61838 0.61838 0 0 0-0.67634-0.12303c0.04094-0.86013-0.27221-1.7117-0.89472-2.3343zm-3.3067 1.0814a0.61838 0.61838 0 0 0-0.015967-0.01364l-0.86984-0.87a0.61838 0.61838 0 0 0-0.042126-0.04213 0.61838 0.61838 0 0 0-0.82551 0.04205l-0.87 0.86984a0.61838 0.61838 0 0 0 0.66145 1.0237c-0.024276 0.84049 0.29182 1.6675 0.90045 2.2762 0.78035 0.78052 1.9205 1.0863 2.9866 0.80073a0.51533 0.51533 0 1 0-0.27202-0.99408c-0.71208 0.19075-1.4669-0.011716-1.988-0.53306-0.45484-0.45491-0.65183-1.0905-0.57258-1.7183l0.018216 0.018221a0.61843 0.61843 0 0 0 0.88935-0.85959z' stroke-width='.68342'/%3E%3Cpath d='m31.219 0.33675v0.00113h-6.9286v1.3295l6.9286 0.036145c0.0026-1.821e-4 0.0053 2.074e-4 0.0079 0 0.0053-4.166e-4 0.01058-0.00137 0.01581-0.00113 0.65203-0.00106 1.1749 0.44619 1.1867 1.0392 0.0108 0.5673-0.60099 1.0888-1.3381 1.0019l-0.0013-0.79858-1.6753 1.5203 1.7016 1.4481-0.0013-0.8031c1.419 0.06127 2.9112-0.90236 2.9081-2.3709-0.0029-1.3197-1.2547-2.4007-2.7961-2.4014-0.0023-1e-6 -0.0043-0.00113-0.0066-0.00113z' stroke-width='.462'/%3E%3Crect x='31.116' y='-1.6777' width='4.3279' height='7.5909'/%3E%3C/g%3E%3C/svg%3E");
+ }
+ .colored .icon-invert {
+ filter: invert(1);
+ }
+</style>
diff --git a/apps/workflowengine/src/components/Rule.vue b/apps/workflowengine/src/components/Rule.vue
new file mode 100644
index 00000000000..76d332ac414
--- /dev/null
+++ b/apps/workflowengine/src/components/Rule.vue
@@ -0,0 +1,249 @@
+<template>
+ <div class="section rule" :style="{ borderLeftColor: operation.color || '' }">
+ <div class="trigger">
+ <p>
+ <span>{{ t('workflowengine', 'When') }}</span>
+ <Event :rule="rule" @update="updateRule" />
+ </p>
+ <p v-for="(check, index) in rule.checks" :key="index">
+ <span>{{ t('workflowengine', 'and') }}</span>
+ <Check :check="check" :rule="rule" @update="updateRule"
+ @remove="removeCheck(check)" />
+ </p>
+ <p>
+ <span />
+ <input v-if="lastCheckComplete" type="button" class="check--add"
+ value="Add a new filter" @click="rule.checks.push({class: null, operator: null, value: null})">
+ </p>
+ </div>
+ <div class="flow-icon icon-confirm" />
+ <div class="action">
+ <div class="buttons">
+ <Actions>
+ <ActionButton v-if="rule.id < -1" icon="icon-close" @click="cancelRule">
+ {{ t('workflowengine', 'Cancel rule creation') }}
+ </ActionButton>
+ <ActionButton v-else icon="icon-close" @click="deleteRule">
+ {{ t('workflowengine', 'Remove rule') }}
+ </ActionButton>
+ </Actions>
+ </div>
+ <Operation :operation="operation" :colored="false">
+ <component :is="operation.options" v-if="operation.options" v-model="rule.operation"
+ @input="updateOperation" />
+ </Operation>
+ <button v-tooltip="ruleStatus.tooltip" class="status-button icon" :class="ruleStatus.class"
+ @click="saveRule">
+ {{ ruleStatus.title }}
+ </button>
+ </div>
+ </div>
+</template>
+
+<script>
+import { Tooltip } from 'nextcloud-vue/dist/Directives/Tooltip'
+import { Actions } from 'nextcloud-vue/dist/Components/Actions'
+import { ActionButton } from 'nextcloud-vue/dist/Components/ActionButton'
+import Event from './Event'
+import Check from './Check'
+import Operation from './Operation'
+
+export default {
+ name: 'Rule',
+ components: {
+ Operation, Check, Event, Actions, ActionButton
+ },
+ directives: {
+ Tooltip
+ },
+ props: {
+ rule: {
+ type: Object,
+ required: true
+ }
+ },
+ data() {
+ return {
+ editing: false,
+ checks: [],
+ error: null,
+ dirty: this.rule.id < 0,
+ checking: false
+ }
+ },
+ computed: {
+ operation() {
+ return this.$store.getters.getOperationForRule(this.rule)
+ },
+ ruleStatus() {
+ if (this.error || !this.rule.valid) {
+ return {
+ title: t('workflowengine', 'The configuration is invalid'),
+ class: 'icon-close-white invalid',
+ tooltip: { placement: 'bottom', show: true, content: this.error }
+ }
+ }
+ if (!this.dirty || this.checking) {
+ return { title: 'Active', class: 'icon icon-checkmark' }
+ }
+ return { title: 'Save', class: 'icon-confirm-white primary' }
+
+ },
+ lastCheckComplete() {
+ const lastCheck = this.rule.checks[this.rule.checks.length - 1]
+ return typeof lastCheck === 'undefined' || lastCheck.class !== null
+ }
+ },
+ methods: {
+ async updateOperation(operation) {
+ this.$set(this.rule, 'operation', operation)
+ await this.updateRule()
+ },
+ async updateRule() {
+ this.checking = true
+ if (!this.dirty) {
+ this.dirty = true
+ }
+ try {
+ // TODO: add new verify endpoint
+ // let result = await axios.post(OC.generateUrl(`/apps/workflowengine/operations/test`), this.rule)
+ this.error = null
+ this.checking = false
+ this.$store.dispatch('updateRule', this.rule)
+ } catch (e) {
+ console.error('Failed to update operation', e)
+ this.error = e.response.ocs.meta.message
+ this.checking = false
+ }
+ },
+ async saveRule() {
+ try {
+ await this.$store.dispatch('pushUpdateRule', this.rule)
+ this.dirty = false
+ this.error = null
+ } catch (e) {
+ console.error('Failed to save operation')
+ this.error = e.response.data.ocs.meta.message
+ }
+ },
+ async deleteRule() {
+ try {
+ await this.$store.dispatch('deleteRule', this.rule)
+ } catch (e) {
+ console.error('Failed to delete operation')
+ this.error = e.response.data.ocs.meta.message
+ }
+ },
+ cancelRule() {
+ this.$store.dispatch('removeRule', this.rule)
+ },
+ async removeCheck(check) {
+ const index = this.rule.checks.findIndex(item => item === check)
+ if (index > -1) {
+ this.$delete(this.rule.checks, index)
+ }
+ this.$store.dispatch('updateRule', this.rule)
+ }
+ }
+}
+</script>
+
+<style scoped lang="scss">
+ button.icon {
+ padding-left: 32px;
+ background-position: 10px center;
+ }
+
+ .status-button {
+ transition: 0.5s ease all;
+ display: block;
+ margin: auto;
+ margin-right: 0;
+ }
+ .status-button.primary {
+ padding-left: 32px;
+ background-position: 10px center;
+ }
+ .status-button:not(.primary) {
+ background-color: var(--color-main-background);
+ }
+ .status-button.invalid {
+ background-color: var(--color-warning);
+ color: #fff;
+ border: none;
+ }
+
+ .flow-icon {
+ width: 44px;
+ }
+
+ .rule {
+ display: flex;
+ flex-wrap: wrap;
+ border-left: 5px solid var(--color-primary-element);
+
+ .trigger, .action {
+ flex-grow: 1;
+ min-height: 100px;
+ max-width: 700px;
+ }
+ .action {
+ max-width: 400px;
+ position: relative;
+ .buttons {
+ position: absolute;
+ right: 0;
+ display: flex;
+ z-index: 1;
+ }
+ }
+ .icon-confirm {
+ background-position: right 27px;
+ padding-right: 20px;
+ margin-right: 20px;
+ }
+ }
+ .trigger p, .action p {
+ min-height: 34px;
+ display: flex;
+ align-items: center;
+
+ & > span {
+ min-width: 50px;
+ text-align: right;
+ color: var(--color-text-maxcontrast);
+ padding-right: 10px;
+ padding-top: 7px;
+ margin-bottom: auto;
+ }
+ .multiselect {
+ flex-grow: 1;
+ max-width: 300px;
+ }
+ }
+
+ .check--add {
+ background-position: 7px center;
+ background-color: transparent;
+ padding-left: 6px;
+ margin: 0;
+ width: 180px;
+ border-radius: var(--border-radius);
+ font-weight: normal;
+ text-align: left;
+ font-size: 1em;
+ }
+
+ @media (max-width:1400px) {
+ .rule {
+ &, .trigger, .action {
+ width: 100%;
+ max-width: 100%;
+ }
+ .flow-icon {
+ display: none;
+ }
+ }
+ }
+
+</style>
diff --git a/apps/workflowengine/src/components/Workflow.vue b/apps/workflowengine/src/components/Workflow.vue
new file mode 100644
index 00000000000..b4fab5a058c
--- /dev/null
+++ b/apps/workflowengine/src/components/Workflow.vue
@@ -0,0 +1,126 @@
+<template>
+ <div id="workflowengine">
+ <div class="section">
+ <h2>{{ t('workflowengine', 'Workflows') }}</h2>
+
+ <transition-group name="slide" tag="div" class="actions">
+ <Operation v-for="operation in getMainOperations" :key="operation.id" :operation="operation"
+ @click.native="createNewRule(operation)" />
+ </transition-group>
+
+ <div v-if="hasMoreOperations" class="actions__more">
+ <button class="icon" :class="showMoreOperations ? 'icon-triangle-n' : 'icon-triangle-s'"
+ @click="showMoreOperations=!showMoreOperations">
+ {{ showMoreOperations ? t('workflowengine', 'Show less') : t('workflowengine', 'Show more') }}
+ </button>
+ </div>
+ </div>
+
+ <transition-group v-if="rules.length > 0" name="slide">
+ <Rule v-for="rule in rules" :key="rule.id" :rule="rule" />
+ </transition-group>
+ </div>
+</template>
+
+<script>
+import Rule from './Rule'
+import Operation from './Operation'
+import { mapGetters, mapState } from 'vuex'
+
+const ACTION_LIMIT = 3
+
+export default {
+ name: 'Workflow',
+ components: {
+ Operation,
+ Rule
+ },
+ data() {
+ return {
+ showMoreOperations: false
+ }
+ },
+ computed: {
+ ...mapGetters({
+ rules: 'getRules'
+ }),
+ ...mapState({
+ operations: 'operations'
+ }),
+ hasMoreOperations() {
+ return Object.keys(this.operations).length > ACTION_LIMIT
+ },
+ getMainOperations() {
+ if (this.showMoreOperations) {
+ return Object.values(this.operations)
+ }
+ return Object.values(this.operations).slice(0, ACTION_LIMIT)
+ }
+ },
+ mounted() {
+ this.$store.dispatch('fetchRules')
+ },
+ methods: {
+ createNewRule(operation) {
+ this.$store.dispatch('createNewRule', operation)
+ }
+ }
+}
+</script>
+
+<style scoped lang="scss">
+ #workflowengine {
+ border-bottom: 1px solid var(--color-border);
+ }
+ .section {
+ max-width: 100vw;
+ }
+ .actions {
+ display: flex;
+ flex-wrap: wrap;
+ max-width: 900px;
+ .actions__item {
+ max-width: 280px;
+ flex-basis: 250px;
+ }
+ }
+
+ button.icon {
+ padding-left: 32px;
+ background-position: 10px center;
+ }
+
+ .slide-enter-active {
+ -moz-transition-duration: 0.3s;
+ -webkit-transition-duration: 0.3s;
+ -o-transition-duration: 0.3s;
+ transition-duration: 0.3s;
+ -moz-transition-timing-function: ease-in;
+ -webkit-transition-timing-function: ease-in;
+ -o-transition-timing-function: ease-in;
+ transition-timing-function: ease-in;
+ }
+
+ .slide-leave-active {
+ -moz-transition-duration: 0.3s;
+ -webkit-transition-duration: 0.3s;
+ -o-transition-duration: 0.3s;
+ transition-duration: 0.3s;
+ -moz-transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
+ -webkit-transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
+ -o-transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
+ transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
+ }
+
+ .slide-enter-to, .slide-leave {
+ max-height: 500px;
+ overflow: hidden;
+ }
+
+ .slide-enter, .slide-leave-to {
+ overflow: hidden;
+ max-height: 0;
+ padding-top: 0;
+ padding-bottom: 0;
+ }
+</style>
diff --git a/apps/workflowengine/src/css/multiselect.css b/apps/workflowengine/src/css/multiselect.css
new file mode 100644
index 00000000000..8eb7583744b
--- /dev/null
+++ b/apps/workflowengine/src/css/multiselect.css
@@ -0,0 +1,11 @@
+.multiselect::v-deep .multiselect__single {
+ display: flex;
+}
+
+.option__icon {
+ min-width: 25px;
+}
+
+input, .multiselect {
+ width: 100%;
+}
diff --git a/apps/workflowengine/src/filemimetypeplugin.js b/apps/workflowengine/src/filemimetypeplugin.js
deleted file mode 100644
index 17c092d209f..00000000000
--- a/apps/workflowengine/src/filemimetypeplugin.js
+++ /dev/null
@@ -1,72 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function() {
-
- OCA.WorkflowEngine = OCA.WorkflowEngine || {};
- OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {};
-
- OCA.WorkflowEngine.Plugins.FileMimeTypePlugin = {
- getCheck: function() {
- return {
- 'class': 'OCA\\WorkflowEngine\\Check\\FileMimeType',
- 'name': t('workflowengine', 'File MIME type'),
- 'operators': [
- {'operator': 'is', 'name': t('workflowengine', 'is')},
- {'operator': '!is', 'name': t('workflowengine', 'is not')},
- {'operator': 'matches', 'name': t('workflowengine', 'matches')},
- {'operator': '!matches', 'name': t('workflowengine', 'does not match')}
- ]
- };
- },
- render: function(element, check) {
- if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\FileMimeType') {
- return;
- }
-
- var placeholder = 'text/plain';
- if (check['operator'] === 'matches' || check['operator'] === '!matches') {
- placeholder = '/^text\\/(plain|html)$/i';
-
- if (this._validateRegex(check['value'])) {
- $(element).removeClass('invalid-input');
- } else {
- $(element).addClass('invalid-input');
- }
- }
-
- $(element).css('width', '250px')
- .attr('placeholder', placeholder)
- .attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: placeholder}))
- .addClass('has-tooltip')
- .tooltip({
- placement: 'bottom'
- });
- },
-
- _validateRegex: function(string) {
- var regexRegex = /^\/(.*)\/([gui]{0,3})$/,
- result = regexRegex.exec(string);
- return result !== null;
- }
- };
-})();
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.FileMimeTypePlugin);
diff --git a/apps/workflowengine/src/filenameplugin.js b/apps/workflowengine/src/filenameplugin.js
deleted file mode 100644
index 7d8018c29cd..00000000000
--- a/apps/workflowengine/src/filenameplugin.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * @copyright Copyright (c) 2018 Daniel Kesselberg <mail@danielkesselberg.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function () {
-
- OCA.WorkflowEngine = OCA.WorkflowEngine || {};
- OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {};
-
- OCA.WorkflowEngine.Plugins.FileNamePlugin = {
- getCheck: function () {
- return {
- 'class': 'OCA\\WorkflowEngine\\Check\\FileName',
- 'name': t('workflowengine', 'File name'),
- 'operators': [
- {'operator': 'is', 'name': t('workflowengine', 'is')},
- {'operator': '!is', 'name': t('workflowengine', 'is not')},
- {
- 'operator': 'matches',
- 'name': t('workflowengine', 'matches')
- },
- {
- 'operator': '!matches',
- 'name': t('workflowengine', 'does not match')
- }
- ]
- };
- },
- render: function (element, check) {
- if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\FileName') {
- return;
- }
-
- var placeholder = 'dummy.jpg';
- if (check['operator'] === 'matches' || check['operator'] === '!matches') {
- placeholder = '/^dummy-.+$/i';
-
- if (this._validateRegex(check['value'])) {
- $(element).removeClass('invalid-input');
- } else {
- $(element).addClass('invalid-input');
- }
- }
-
- $(element).css('width', '250px')
- .attr('placeholder', placeholder)
- .attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: placeholder}))
- .addClass('has-tooltip')
- .tooltip({
- placement: 'bottom'
- });
- },
-
- _validateRegex: function (string) {
- var regexRegex = /^\/(.*)\/([gui]{0,3})$/,
- result = regexRegex.exec(string);
- return result !== null;
- }
- };
-})();
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.FileNamePlugin); \ No newline at end of file
diff --git a/apps/workflowengine/src/filesizeplugin.js b/apps/workflowengine/src/filesizeplugin.js
deleted file mode 100644
index 0efa9d00edf..00000000000
--- a/apps/workflowengine/src/filesizeplugin.js
+++ /dev/null
@@ -1,56 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function() {
-
- OCA.WorkflowEngine = OCA.WorkflowEngine || {};
- OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {};
-
- OCA.WorkflowEngine.Plugins.FileSizePlugin = {
- getCheck: function() {
- return {
- 'class': 'OCA\\WorkflowEngine\\Check\\FileSize',
- 'name': t('workflowengine', 'File size (upload)'),
- 'operators': [
- {'operator': 'less', 'name': t('workflowengine', 'less')},
- {'operator': '!greater', 'name': t('workflowengine', 'less or equals')},
- {'operator': '!less', 'name': t('workflowengine', 'greater or equals')},
- {'operator': 'greater', 'name': t('workflowengine', 'greater')}
- ]
- };
- },
- render: function(element, check) {
- if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\FileSize') {
- return;
- }
-
- var placeholder = '12 MB'; // Do not translate!!!
- $(element).css('width', '250px')
- .attr('placeholder', placeholder)
- .attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: placeholder}))
- .addClass('has-tooltip')
- .tooltip({
- placement: 'bottom'
- });
- }
- };
-})();
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.FileSizePlugin);
diff --git a/apps/workflowengine/src/filesystemtagsplugin.js b/apps/workflowengine/src/filesystemtagsplugin.js
deleted file mode 100644
index e66a35b73b9..00000000000
--- a/apps/workflowengine/src/filesystemtagsplugin.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function() {
-
- OCA.WorkflowEngine = OCA.WorkflowEngine || {};
- OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {};
-
- OCA.WorkflowEngine.Plugins.FileSystemTagsPlugin = {
- getCheck: function() {
- this.collection = OC.SystemTags.collection;
-
- return {
- 'class': 'OCA\\WorkflowEngine\\Check\\FileSystemTags',
- 'name': t('workflowengine', 'File system tag'),
- 'operators': [
- {'operator': 'is', 'name': t('workflowengine', 'is tagged with')},
- {'operator': '!is', 'name': t('workflowengine', 'is not tagged with')}
- ]
- };
- },
- render: function(element, check) {
- if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\FileSystemTags') {
- return;
- }
-
- $(element).css('width', '400px');
-
- $(element).select2({
- allowClear: false,
- multiple: false,
- placeholder: t('workflowengine', 'Select tag…'),
- query: _.debounce(function(query) {
- query.callback({
- results: OC.SystemTags.collection.filterByName(query.term)
- });
- }, 100, true),
- id: function(element) {
- return element.get('id');
- },
- initSelection: function(element, callback) {
- callback($(element).val());
- },
- formatResult: function (tag) {
- return OC.SystemTags.getDescriptiveTag(tag);
- },
- formatSelection: function (tagId) {
- var tag = OC.SystemTags.collection.get(tagId);
- if (!_.isUndefined(tag)) {
- return OC.SystemTags.getDescriptiveTag(tag);
- }
- },
- escapeMarkup: function(m) {
- return m;
- }
- });
- }
- };
-})();
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.FileSystemTagsPlugin);
diff --git a/apps/workflowengine/src/hbs_helpers/getOperators.js b/apps/workflowengine/src/hbs_helpers/getOperators.js
deleted file mode 100644
index 4e3606fe82c..00000000000
--- a/apps/workflowengine/src/hbs_helpers/getOperators.js
+++ /dev/null
@@ -1,7 +0,0 @@
-module.exports = function(classname) {
- var check = OCA.WorkflowEngine.getCheckByClass(classname);
- if (!_.isUndefined(check)) {
- return check['operators'];
- }
- return [];
-}
diff --git a/apps/workflowengine/src/hbs_helpers/selectItem.js b/apps/workflowengine/src/hbs_helpers/selectItem.js
deleted file mode 100644
index 594e3debadd..00000000000
--- a/apps/workflowengine/src/hbs_helpers/selectItem.js
+++ /dev/null
@@ -1,7 +0,0 @@
-module.exports = function(currentValue, itemValue) {
- if (currentValue === itemValue) {
- return 'selected="selected"';
- }
-
- return "";
-}
diff --git a/apps/workflowengine/src/helpers/api.js b/apps/workflowengine/src/helpers/api.js
new file mode 100644
index 00000000000..c2c8d9b6b49
--- /dev/null
+++ b/apps/workflowengine/src/helpers/api.js
@@ -0,0 +1,30 @@
+/*
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+const getApiUrl = (url) => {
+ const scopeValue = OCP.InitialState.loadState('workflowengine', 'scope') === 0 ? 'global' : 'user'
+ return OC.linkToOCS('apps/workflowengine/api/v1/workflows', 2) + scopeValue + url + '?format=json'
+}
+
+export {
+ getApiUrl
+}
diff --git a/apps/workflowengine/src/helpers/validators.js b/apps/workflowengine/src/helpers/validators.js
new file mode 100644
index 00000000000..5fb94f66ecf
--- /dev/null
+++ b/apps/workflowengine/src/helpers/validators.js
@@ -0,0 +1,48 @@
+/*
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+const validateRegex = function(string) {
+ var regexRegex = /^\/(.*)\/([gui]{0,3})$/
+ var result = regexRegex.exec(string)
+ return result !== null
+}
+
+const validateIPv4 = function(string) {
+ var regexRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(3[0-2]|[1-2][0-9]|[1-9])$/
+ var result = regexRegex.exec(string)
+ return result !== null
+}
+
+const validateIPv6 = function(string) {
+ var regexRegex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9])$/
+ var result = regexRegex.exec(string)
+ return result !== null
+}
+
+const stringValidator = (check) => {
+ if (check.operator === 'matches' || check.operator === '!matches') {
+ return validateRegex(check.value)
+ }
+ return true
+}
+
+export { validateRegex, stringValidator, validateIPv4, validateIPv6 }
diff --git a/apps/workflowengine/src/mixins/valueMixin.js b/apps/workflowengine/src/mixins/valueMixin.js
new file mode 100644
index 00000000000..e6ea5bbdcf4
--- /dev/null
+++ b/apps/workflowengine/src/mixins/valueMixin.js
@@ -0,0 +1,54 @@
+/*
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+const valueMixin = {
+ props: {
+ value: {
+ type: String,
+ default: ''
+ },
+ check: {
+ type: Object,
+ default: () => { return {} }
+ }
+ },
+ data() {
+ return {
+ newValue: ''
+ }
+ },
+ watch: {
+ value: {
+ immediate: true,
+ handler: function(value) {
+ this.updateInternalValue(value)
+ }
+ }
+ },
+ methods: {
+ updateInternalValue(value) {
+ this.newValue = value
+ }
+ }
+}
+
+export default valueMixin
diff --git a/apps/workflowengine/src/requestremoteaddressplugin.js b/apps/workflowengine/src/requestremoteaddressplugin.js
deleted file mode 100644
index a66d6f51f0f..00000000000
--- a/apps/workflowengine/src/requestremoteaddressplugin.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function() {
-
- OCA.WorkflowEngine = OCA.WorkflowEngine || {};
- OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {};
-
- OCA.WorkflowEngine.Plugins.RequestRemoteAddressPlugin = {
- getCheck: function() {
- return {
- 'class': 'OCA\\WorkflowEngine\\Check\\RequestRemoteAddress',
- 'name': t('workflowengine', 'Request remote address'),
- 'operators': [
- {'operator': 'matchesIPv4', 'name': t('workflowengine', 'matches IPv4')},
- {'operator': '!matchesIPv4', 'name': t('workflowengine', 'does not match IPv4')},
- {'operator': 'matchesIPv6', 'name': t('workflowengine', 'matches IPv6')},
- {'operator': '!matchesIPv6', 'name': t('workflowengine', 'does not match IPv6')}
- ]
- };
- },
- render: function(element, check) {
- if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\RequestRemoteAddress') {
- return;
- }
-
- var placeholder = '127.0.0.1/32'; // Do not translate!!!
- if (check['operator'] === 'matchesIPv6' || check['operator'] === '!matchesIPv6') {
- placeholder = '::1/128'; // Do not translate!!!
- if (this._validateIPv6(check['value'])) {
- $(element).removeClass('invalid-input');
- } else {
- $(element).addClass('invalid-input');
- }
- } else {
- if (this._validateIPv4(check['value'])) {
- $(element).removeClass('invalid-input');
- } else {
- $(element).addClass('invalid-input');
- }
- }
-
- $(element).css('width', '300px')
- .attr('placeholder', placeholder)
- .attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: placeholder}))
- .addClass('has-tooltip')
- .tooltip({
- placement: 'bottom'
- });
- },
-
- _validateIPv4: function(string) {
- var regexRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(3[0-2]|[1-2][0-9]|[1-9])$/,
- result = regexRegex.exec(string);
- return result !== null;
- },
-
- _validateIPv6: function(string) {
- var regexRegex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/(1([01][0-9]|2[0-8])|[1-9][0-9]|[0-9])$/,
- result = regexRegex.exec(string);
- return result !== null;
- }
- };
-})();
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.RequestRemoteAddressPlugin);
diff --git a/apps/workflowengine/src/requesttimeplugin.js b/apps/workflowengine/src/requesttimeplugin.js
deleted file mode 100644
index 111b2bb7437..00000000000
--- a/apps/workflowengine/src/requesttimeplugin.js
+++ /dev/null
@@ -1,196 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function() {
-
- OCA.WorkflowEngine = OCA.WorkflowEngine || {};
- OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {};
-
- OCA.WorkflowEngine.Plugins.RequestTimePlugin = {
- timezones: [
- "Europe/Berlin",
- "Europe/London"
- ],
- _$element: null,
- getCheck: function() {
- return {
- 'class': 'OCA\\WorkflowEngine\\Check\\RequestTime',
- 'name': t('workflowengine', 'Request time'),
- 'operators': [
- {'operator': 'in', 'name': t('workflowengine', 'between')},
- {'operator': '!in', 'name': t('workflowengine', 'not between')}
- ]
- };
- },
- render: function(element, check) {
- if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\RequestTime') {
- return;
- }
-
- var startTime = '09:00',
- endTime = '18:00',
- timezone = jstz.determine().name(),
- $element = $(element);
-
- if (_.isString(check['value']) && check['value'] !== '') {
- var value = JSON.parse(check['value']),
- splittedStart = value[0].split(' ', 2),
- splittedEnd = value[1].split(' ', 2);
-
- startTime = splittedStart[0];
- endTime = splittedEnd[0];
- timezone = splittedStart[1];
- }
-
- var valueJSON = JSON.stringify([startTime + ' ' + timezone, endTime + ' ' + timezone]);
- if (check['value'] !== valueJSON) {
- check['value'] = valueJSON;
- $element.val(valueJSON);
- }
-
- $element.css('display', 'none');
-
- $('<input>')
- .attr('type', 'text')
- .attr('placeholder', t('workflowengine', 'Start'))
- .attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: '16:00'}))
- .addClass('has-tooltip')
- .tooltip({
- placement: 'bottom'
- })
- .addClass('start')
- .val(startTime)
- .insertBefore($element);
- $('<input>')
- .attr('type', 'text')
- .attr('placeholder', t('workflowengine', 'End'))
- .attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: '16:00'}))
- .addClass('has-tooltip')
- .tooltip({
- placement: 'bottom'
- })
- .addClass('end')
- .val(endTime)
- .insertBefore($element);
-
- var timezoneInput = $('<input>')
- .attr('type', 'hidden')
- .css('width', '250px')
- .insertBefore($element)
- .val(timezone);
-
- timezoneInput.select2({
- allowClear: false,
- multiple: false,
- placeholder: t('workflowengine', 'Select timezone…'),
- ajax: {
- url: OC.generateUrl('apps/workflowengine/timezones'),
- dataType: 'json',
- quietMillis: 100,
- data: function (term) {
- if (term === '') {
- // Default search in the same continent...
- term = jstz.determine().name().split('/');
- term = term[0];
- }
- return {
- search: term
- };
- },
- results: function (response) {
- var results = [];
- $.each(response, function(timezone) {
- results.push({ id: timezone });
- });
-
- return {
- results: results,
- more: false
- };
- }
- },
- initSelection: function (element, callback) {
- callback(element.val());
- },
- formatResult: function (element) {
- return '<span>' + element.id + '</span>';
- },
- formatSelection: function (element) {
- if (!_.isUndefined(element.id)) {
- element = element.id;
- }
- return '<span>' + element + '</span>';
- }
- });
-
- // Has to be added after select2 for `event.target.classList`
- timezoneInput.addClass('timezone');
-
- $element.parent()
- .on('change', '.start', _.bind(this.update, this))
- .on('change', '.end', _.bind(this.update, this))
- .on('change', '.timezone', _.bind(this.update, this));
-
- this._$element = $element;
- },
- update: function(event) {
- var value = event.target.value,
- key = null;
-
- for (var i = 0; i < event.target.classList.length; i++) {
- key = event.target.classList[i];
- }
-
- if (key === null) {
- console.warn('update triggered but element doesn\'t have any class');
- return;
- }
-
- var data = JSON.parse(this._$element.val()),
- startTime = moment(data[0].split(' ', 2)[0], 'H:m Z'),
- endTime = moment(data[1].split(' ', 2)[0], 'H:m Z'),
- timezone = data[0].split(' ', 2)[1];
-
- if (key === 'start' || key === 'end') {
- var parsedDate = moment(value, ['H:m', 'h:m a'], true).format('HH:mm');
-
- if (parsedDate === 'Invalid date') {
- return;
- }
-
- var indexValue = 0;
- if (key === 'end') {
- indexValue = 1;
- }
- data[indexValue] = parsedDate + ' ' + timezone;
- }
-
- if (key === 'timezone') {
- data[0] = startTime.format('HH:mm') + ' ' + value;
- data[1] = endTime.format('HH:mm') + ' ' + value;
- }
-
- this._$element.val(JSON.stringify(data));
- this._$element.trigger('change');
- }
- };
-})();
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.RequestTimePlugin);
diff --git a/apps/workflowengine/src/requesturlplugin.js b/apps/workflowengine/src/requesturlplugin.js
deleted file mode 100644
index 7c81deaaf33..00000000000
--- a/apps/workflowengine/src/requesturlplugin.js
+++ /dev/null
@@ -1,117 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function() {
-
- OCA.WorkflowEngine = OCA.WorkflowEngine || {};
- OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {};
-
- OCA.WorkflowEngine.Plugins.RequestURLPlugin = {
- predefinedValues: ['webdav'],
- getCheck: function() {
- return {
- 'class': 'OCA\\WorkflowEngine\\Check\\RequestURL',
- 'name': t('workflowengine', 'Request URL'),
- 'operators': [
- {'operator': 'is', 'name': t('workflowengine', 'is')},
- {'operator': '!is', 'name': t('workflowengine', 'is not')},
- {'operator': 'matches', 'name': t('workflowengine', 'matches')},
- {'operator': '!matches', 'name': t('workflowengine', 'does not match')}
- ]
- };
- },
- render: function(element, check) {
- if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\RequestURL') {
- return;
- }
-
- var placeholder = 'https://localhost/index.php';
-
- if (check['operator'] === 'matches' || check['operator'] === '!matches') {
- placeholder = '/^https\\:\\/\\/localhost\\/index\\.php$/i';
- }
-
- $(element).css('width', '250px')
- .attr('placeholder', placeholder)
- .attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: placeholder}))
- .addClass('has-tooltip')
- .tooltip({
- placement: 'bottom'
- });
-
- if (check['operator'] === 'matches' || check['operator'] === '!matches') {
- if (this._validateRegex(check['value'])) {
- $(element).removeClass('invalid-input');
- } else {
- $(element).addClass('invalid-input');
- }
- } else {
- var self = this,
- data = [
- {
- text: t('workflowengine', 'Predefined URLs'),
- children: [
- {id: 'webdav', text: t('workflowengine', 'Files WebDAV')}
- ]
- }
- ];
- if (this.predefinedValues.indexOf(check['value']) === -1) {
- data.unshift({
- id: check['value'],
- text: check['value']
- })
- }
-
-
- $(element).select2({
- data: data,
- createSearchChoice: function(term) {
- if (self.predefinedValues.indexOf(check['value']) === -1) {
- return {
- id: term,
- text: term
- };
- }
- },
- id: function(element) {
- return element.id;
- },
- formatResult: function (tag) {
- return tag.text;
- },
- formatSelection: function (tag) {
- return tag.text;
- },
- escapeMarkup: function(m) {
- return m;
- }
- })
- }
- },
-
- _validateRegex: function(string) {
- var regexRegex = /^\/(.*)\/([gui]{0,3})$/,
- result = regexRegex.exec(string);
- return result !== null;
- }
- };
-})();
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.RequestURLPlugin);
diff --git a/apps/workflowengine/src/requestuseragentplugin.js b/apps/workflowengine/src/requestuseragentplugin.js
deleted file mode 100644
index 881ea4b8ac7..00000000000
--- a/apps/workflowengine/src/requestuseragentplugin.js
+++ /dev/null
@@ -1,119 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function() {
-
- OCA.WorkflowEngine = OCA.WorkflowEngine || {};
- OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {};
-
- OCA.WorkflowEngine.Plugins.RequestUserAgentPlugin = {
- predefinedValues: ['android', 'ios', 'desktop'],
- getCheck: function() {
- return {
- 'class': 'OCA\\WorkflowEngine\\Check\\RequestUserAgent',
- 'name': t('workflowengine', 'Request user agent'),
- 'operators': [
- {'operator': 'is', 'name': t('workflowengine', 'is')},
- {'operator': '!is', 'name': t('workflowengine', 'is not')},
- {'operator': 'matches', 'name': t('workflowengine', 'matches')},
- {'operator': '!matches', 'name': t('workflowengine', 'does not match')}
- ]
- };
- },
- render: function(element, check) {
- if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\RequestUserAgent') {
- return;
- }
-
- var placeholder = 'Mozilla/5.0 User Agent';
-
- if (check.operator === 'matches' || check.operator === '!matches') {
- placeholder = '/^Mozilla\\/5\\.0 (.*)$/i';
- }
-
- $(element).css('width', '250px')
- .attr('placeholder', placeholder)
- .attr('title', t('workflowengine', 'Example: {placeholder}', {placeholder: placeholder}))
- .addClass('has-tooltip')
- .tooltip({
- placement: 'bottom'
- });
-
- if (check.operator === 'matches' || check.operator === '!matches') {
- if (this._validateRegex(check.value)) {
- $(element).removeClass('invalid-input');
- } else {
- $(element).addClass('invalid-input');
- }
- } else {
- var self = this,
- data = [
- {
- text: t('workflowengine', 'Sync clients'),
- children: [
- {id: 'android', text: t('workflowengine', 'Android client')},
- {id: 'ios', text: t('workflowengine', 'iOS client')},
- {id: 'desktop', text: t('workflowengine', 'Desktop client')},
- {id: 'mail', text: t('workflowengine', 'Thunderbird & Outlook addons')}
- ]
- }
- ];
- if (this.predefinedValues.indexOf(check.value) === -1) {
- data.unshift({
- id: check.value,
- text: check.value
- });
- }
-
- $(element).select2({
- data: data,
- createSearchChoice: function(term) {
- if (self.predefinedValues.indexOf(check.value) === -1) {
- return {
- id: term,
- text: term
- };
- }
- },
- id: function(element) {
- return element.id;
- },
- formatResult: function (tag) {
- return tag.text;
- },
- formatSelection: function (tag) {
- return tag.text;
- },
- escapeMarkup: function(m) {
- return m;
- }
- })
- }
- },
-
- _validateRegex: function(string) {
- var regexRegex = /^\/(.*)\/([gui]{0,3})$/,
- result = regexRegex.exec(string);
- return result !== null;
- }
- };
-})();
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.RequestUserAgentPlugin);
diff --git a/apps/workflowengine/src/store.js b/apps/workflowengine/src/store.js
new file mode 100644
index 00000000000..a322c7fb3ea
--- /dev/null
+++ b/apps/workflowengine/src/store.js
@@ -0,0 +1,164 @@
+/*
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import Vue from 'vue'
+import Vuex from 'vuex'
+import axios from 'nextcloud-axios'
+import { getApiUrl } from './helpers/api'
+import confirmPassword from 'nextcloud-password-confirmation'
+
+Vue.use(Vuex)
+
+const store = new Vuex.Store({
+ state: {
+ rules: [],
+ scope: OCP.InitialState.loadState('workflowengine', 'scope'),
+ operations: OCP.InitialState.loadState('workflowengine', 'operators'),
+
+ plugins: Vue.observable({
+ checks: {},
+ operators: {}
+ }),
+
+ entities: OCP.InitialState.loadState('workflowengine', 'entities'),
+ events: OCP.InitialState.loadState('workflowengine', 'entities')
+ .map((entity) => entity.events.map(event => {
+ return {
+ id: `${entity.id}::${event.eventName}`,
+ entity,
+ ...event
+ }
+ })).flat(),
+ checks: OCP.InitialState.loadState('workflowengine', 'checks')
+ },
+ mutations: {
+ addRule(state, rule) {
+ state.rules.push({ ...rule, valid: true })
+ },
+ updateRule(state, rule) {
+ const index = state.rules.findIndex((item) => rule.id === item.id)
+ const newRule = Object.assign({}, rule)
+ Vue.set(state.rules, index, newRule)
+ },
+ removeRule(state, rule) {
+ const index = state.rules.findIndex((item) => rule.id === item.id)
+ state.rules.splice(index, 1)
+ },
+ addPluginCheck(state, plugin) {
+ Vue.set(state.plugins.checks, plugin.class, plugin)
+ },
+ addPluginOperator(state, plugin) {
+ plugin = Object.assign(
+ { color: 'var(--color-primary-element)' },
+ plugin, state.operations[plugin.id] || {})
+ Vue.set(state.operations, plugin.id, plugin)
+ }
+ },
+ actions: {
+ async fetchRules(context) {
+ const { data } = await axios.get(getApiUrl(''))
+ Object.values(data.ocs.data).flat().forEach((rule) => {
+ context.commit('addRule', rule)
+ })
+ },
+ createNewRule(context, rule) {
+ let entity = null
+ let events = []
+ if (rule.isComplex === false && rule.fixedEntity === '') {
+ entity = context.state.entities.find((item) => rule.entities && rule.entities[0] === item.id)
+ entity = entity || Object.values(context.state.entities)[0]
+ events = [entity.events[0].eventName]
+ }
+
+ context.commit('addRule', {
+ id: -(new Date().getTime()),
+ class: rule.id,
+ entity: entity ? entity.id : rule.fixedEntity,
+ events,
+ name: '', // unused in the new ui, there for legacy reasons
+ checks: [],
+ operation: rule.operation || ''
+ })
+ },
+ updateRule(context, rule) {
+ context.commit('updateRule', {
+ ...rule,
+ events: typeof rule.events === 'string' ? JSON.parse(rule.events) : rule.events
+ })
+ },
+ removeRule(context, rule) {
+ context.commit('removeRule', rule)
+ },
+ async pushUpdateRule(context, rule) {
+ await confirmPassword()
+ let result
+ if (rule.id < 0) {
+ result = await axios.post(getApiUrl(''), rule)
+ } else {
+ result = await axios.put(getApiUrl(`/${rule.id}`), rule)
+ }
+ Vue.set(rule, 'id', result.data.ocs.data.id)
+ context.commit('updateRule', rule)
+ },
+ async deleteRule(context, rule) {
+ await confirmPassword()
+ await axios.delete(getApiUrl(`/${rule.id}`))
+ context.commit('removeRule', rule)
+ },
+ setValid(context, { rule, valid }) {
+ rule.valid = valid
+ context.commit('updateRule', rule)
+ }
+ },
+ getters: {
+ getRules(state) {
+ return state.rules.sort((rule1, rule2) => {
+ return rule1.id - rule2.id || rule2.class - rule1.class
+ })
+ },
+ getOperationForRule(state) {
+ return (rule) => state.operations[rule.class]
+ },
+ getEntityForOperation(state) {
+ return (operation) => state.entities.find((entity) => operation.fixedEntity === entity.id)
+ },
+ getEventsForOperation(state) {
+ return (operation) => state.events
+ },
+ /**
+ * Return all available checker plugins for a given entity class
+ */
+ getChecksForEntity(state) {
+ return (entity) => {
+ return state.checks
+ .filter((check) => check.supportedEntities.indexOf(entity) > -1 || check.supportedEntities.length === 0)
+ .map((check) => state.plugins.checks[check.id])
+ .reduce((obj, item) => {
+ obj[item.class] = item
+ return obj
+ }, {})
+ }
+ }
+ }
+})
+
+export default store
diff --git a/apps/workflowengine/src/templates/operation.handlebars b/apps/workflowengine/src/templates/operation.handlebars
deleted file mode 100644
index 0899890cef2..00000000000
--- a/apps/workflowengine/src/templates/operation.handlebars
+++ /dev/null
@@ -1,45 +0,0 @@
-<div class="operation{{#if hasChanged}} modified{{/if}}">
- <div class="operation-header">
- <input type="text" class="operation-name" placeholder="{{shortRuleDescTXT}}" value="{{operation.name}}" />
- <input type="text" class="operation-operation" value="{{operation.operation}}" />
- {{! delete only makes sense if the operation is already saved }}
- {{#if operation.id}}
- <span class="button-delete icon-delete"></span>
- {{/if}}
- </div>
-
- <div class="checks">
- {{#each operation.checks}}
- <div class="check" data-id="{{@index}}">
- <select class="check-class">
- {{#each ../classes}}
- <option value="{{class}}" {{{selectItem class ../class}}}>{{name}}</option>
- {{/each}}
- </select>
- <select class="check-operator">
- {{#each (getOperators class)}}
- <option value="{{operator}}" {{{selectItem operator ../operator}}}>{{name}}</option>
- {{/each}}
- </select>
- <input type="text" class="check-value" value="{{value}}">
- <span class="button-delete-check icon-delete"></span>
- </div>
- {{/each}}
- </div>
- <button class="button-add">{{addRuleTXT}}</button>
- {{#if hasChanged}}
- {{! reset only makes sense if the operation is already saved }}
- {{#if operation.id}}
- <button class="button-reset pull-right">{{resetTXT}}</button>
- {{/if}}
- <button class="button-save pull-right">{{saveTXT}}</button>
- {{/if}}
- {{#if saving}}
- <span class="icon-loading-small pull-right"></span>
- <span class="pull-right">{{savingTXT}}</span>
- {{else}}{{#if message}}
- <span class="msg pull-right {{#if errorMessage}}error{{else}}success{{/if}}">
- {{message}}{{#if errorMessage}} {{errorMessage}}{{/if}}
- </span>
- {{/if}}{{/if}}
-</div>
diff --git a/apps/workflowengine/src/templates/operations.handlebars b/apps/workflowengine/src/templates/operations.handlebars
deleted file mode 100644
index 14b62ee79a6..00000000000
--- a/apps/workflowengine/src/templates/operations.handlebars
+++ /dev/null
@@ -1,2 +0,0 @@
-<div class="operations"></div>
-<button class="button-add-operation">{{addRuleGroupTXT}}</button>
diff --git a/apps/workflowengine/src/usergroupmembershipplugin.js b/apps/workflowengine/src/usergroupmembershipplugin.js
deleted file mode 100644
index 53f35fedf2d..00000000000
--- a/apps/workflowengine/src/usergroupmembershipplugin.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-(function() {
-
- OCA.WorkflowEngine = OCA.WorkflowEngine || {};
- OCA.WorkflowEngine.Plugins = OCA.WorkflowEngine.Plugins || {};
-
- OCA.WorkflowEngine.Plugins.UserGroupMembershipPlugin = {
- getCheck: function() {
- return {
- 'class': 'OCA\\WorkflowEngine\\Check\\UserGroupMembership',
- 'name': t('workflowengine', 'User group membership'),
- 'operators': [
- {'operator': 'is', 'name': t('workflowengine', 'is member of')},
- {'operator': '!is', 'name': t('workflowengine', 'is not member of')}
- ]
- };
- },
- render: function(element, check, groups) {
- if (check['class'] !== 'OCA\\WorkflowEngine\\Check\\UserGroupMembership') {
- return;
- }
-
- $(element).css('width', '400px');
-
- $(element).select2({
- data: { results: groups, text: 'displayname' },
- initSelection: function (element, callback) {
- var groupId = element.val();
- if (groupId && groups.length > 0) {
- callback({
- id: groupId,
- displayname: groups.find(function (group) {
- return group.id === groupId;
- }).displayname
- });
- } else if (groupId) {
- callback({
- id: groupId,
- displayname: groupId
- });
- } else {
- callback();
- }
- },
- formatResult: function (element) {
- return '<span>' + escapeHTML(element.displayname) + '</span>';
- },
- formatSelection: function (element) {
- return '<span title="'+escapeHTML(element.id)+'">'+escapeHTML(element.displayname)+'</span>';
- }
- });
- }
- };
-})();
-
-OC.Plugins.register('OCA.WorkflowEngine.CheckPlugins', OCA.WorkflowEngine.Plugins.UserGroupMembershipPlugin);
diff --git a/apps/workflowengine/src/workflowengine.js b/apps/workflowengine/src/workflowengine.js
index 207d2311bcc..2eb8161fc87 100644
--- a/apps/workflowengine/src/workflowengine.js
+++ b/apps/workflowengine/src/workflowengine.js
@@ -1,12 +1,70 @@
-import './admin'
-import './filemimetypeplugin'
-import './filenameplugin'
-import './filesizeplugin'
-import './filesystemtagsplugin'
-import './requestremoteaddressplugin'
-import './requesttimeplugin'
-import './requesturlplugin'
-import './requestuseragentplugin'
-import './usergroupmembershipplugin'
-
-window.OCA.WorkflowEngine = OCA.WorkflowEngine
+import Vue from 'vue'
+import Vuex from 'vuex'
+import store from './store'
+import Settings from './components/Workflow'
+import ShippedChecks from './components/Checks'
+
+/**
+ * A plugin for displaying a custom value field for checks
+ *
+ * @typedef {Object} CheckPlugin
+ * @property {string} class - The PHP class name of the check
+ * @property {Comparison[]} operators - A list of possible comparison operations running on the check
+ * @property {Vue} component - A vue component to handle the rendering of options
+ * The component should handle the v-model directive properly,
+ * so it needs a value property to receive data and emit an input
+ * event once the data has changed
+ * @property {callable} placeholder - Return a placeholder of no custom component is used
+ * @property {callable} validate - validate a check if no custom component is used
+ **/
+
+/**
+ * A plugin for extending the admin page repesentation of a operator
+ *
+ * @typedef {Object} OperatorPlugin
+ * @property {string} id - The PHP class name of the check
+ * @property {string} operation - Default value for the operation field
+ * @property {string} color - Custom color code to be applied for the operator selector
+ * @property {Vue} component - A vue component to handle the rendering of options
+ * The component should handle the v-model directive properly,
+ * so it needs a value property to receive data and emit an input
+ * event once the data has changed
+ */
+
+/**
+ * @typedef {Object} Comparison
+ * @property {string} operator - value the comparison should have, e.g. !less, greater
+ * @property {string} name - Translated readable text, e.g. less or equals
+ **/
+
+/**
+ * Public javascript api for apps to register custom plugins
+ */
+window.OCA.WorkflowEngine = Object.assign({}, OCA.WorkflowEngine, {
+
+ /**
+ *
+ * @param {CheckPlugin} Plugin
+ */
+ registerCheck: function (Plugin) {
+ store.commit('addPluginCheck', Plugin)
+ },
+ /**
+ *
+ * @param {OperatorPlugin} Plugin
+ */
+ registerOperator: function (Plugin) {
+ store.commit('addPluginOperator', Plugin)
+ }
+})
+
+// Register shipped checks
+ShippedChecks.forEach((checkPlugin) => window.OCA.WorkflowEngine.registerCheck(checkPlugin))
+
+Vue.use(Vuex)
+Vue.prototype.t = t
+
+const View = Vue.extend(Settings)
+new View({
+ store
+}).$mount('#workflowengine')
diff --git a/apps/workflowengine/templates/settings.php b/apps/workflowengine/templates/settings.php
index 04f43bb2573..855731af661 100644
--- a/apps/workflowengine/templates/settings.php
+++ b/apps/workflowengine/templates/settings.php
@@ -22,4 +22,4 @@
/** @var array $_ */
/** @var \OCP\IL10N $l */
?>
-<div id="<?php p($_['appid']); ?>" class="<? p(\OCA\WorkflowEngine\AppInfo\Application::APP_ID); ?>"></div>
+<div id="<?php p(\OCA\WorkflowEngine\AppInfo\Application::APP_ID); ?>"></div>
diff --git a/apps/workflowengine/webpack.js b/apps/workflowengine/webpack.js
index 76e46261f93..bbd5efa9799 100644
--- a/apps/workflowengine/webpack.js
+++ b/apps/workflowengine/webpack.js
@@ -7,17 +7,5 @@ module.exports = {
publicPath: '/js/',
filename: 'workflowengine.js',
jsonpFunction: 'webpackJsonpWorkflowengine'
- },
- module: {
- rules: [
- {
- test: /\.handlebars/,
- loader: "handlebars-loader",
- query: {
- extensions: '.handlebars',
- helperDirs: path.join(__dirname, 'src/hbs_helpers'),
- }
- }
- ]
}
}
diff --git a/package-lock.json b/package-lock.json
index 157f490c41b..3681b527582 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5,9 +5,9 @@
"requires": true,
"dependencies": {
"@babel/code-frame": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
- "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==",
+ "version": "7.5.5",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
+ "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
"dev": true,
"requires": {
"@babel/highlight": "^7.0.0"
@@ -35,73 +35,12 @@
"source-map": "^0.5.0"
},
"dependencies": {
- "@babel/code-frame": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
- "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
- "dev": true,
- "requires": {
- "@babel/highlight": "^7.0.0"
- }
- },
- "@babel/generator": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.0.tgz",
- "integrity": "sha512-Ms8Mo7YBdMMn1BYuNtKuP/z0TgEIhbcyB8HVR6PPNYp4P61lMsABiS4A3VG1qznjXVCf3r+fVHhm4efTYVsySA==",
- "dev": true,
- "requires": {
- "@babel/types": "^7.6.0",
- "jsesc": "^2.5.1",
- "lodash": "^4.17.13",
- "source-map": "^0.5.0",
- "trim-right": "^1.0.1"
- }
- },
"@babel/parser": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.0.tgz",
"integrity": "sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ==",
"dev": true
},
- "@babel/template": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz",
- "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==",
- "dev": true,
- "requires": {
- "@babel/code-frame": "^7.0.0",
- "@babel/parser": "^7.6.0",
- "@babel/types": "^7.6.0"
- }
- },
- "@babel/traverse": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.0.tgz",
- "integrity": "sha512-93t52SaOBgml/xY74lsmt7xOR4ufYvhb5c5qiM6lu4J/dWGMAfAh6eKw4PjLes6DI6nQgearoxnFJk60YchpvQ==",
- "dev": true,
- "requires": {
- "@babel/code-frame": "^7.5.5",
- "@babel/generator": "^7.6.0",
- "@babel/helper-function-name": "^7.1.0",
- "@babel/helper-split-export-declaration": "^7.4.4",
- "@babel/parser": "^7.6.0",
- "@babel/types": "^7.6.0",
- "debug": "^4.1.0",
- "globals": "^11.1.0",
- "lodash": "^4.17.13"
- }
- },
- "@babel/types": {
- "version": "7.6.1",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
- "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
- "dev": true,
- "requires": {
- "esutils": "^2.0.2",
- "lodash": "^4.17.13",
- "to-fast-properties": "^2.0.0"
- }
- },
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
@@ -132,17 +71,6 @@
"trim-right": "^1.0.1"
},
"dependencies": {
- "@babel/types": {
- "version": "7.6.1",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
- "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
- "dev": true,
- "requires": {
- "esutils": "^2.0.2",
- "lodash": "^4.17.13",
- "to-fast-properties": "^2.0.0"
- }
- },
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -190,19 +118,6 @@
"@babel/helper-function-name": "^7.1.0",
"@babel/types": "^7.5.5",
"lodash": "^4.17.13"
- },
- "dependencies": {
- "@babel/types": {
- "version": "7.6.1",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
- "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
- "dev": true,
- "requires": {
- "esutils": "^2.0.2",
- "lodash": "^4.17.13",
- "to-fast-properties": "^2.0.0"
- }
- }
}
},
"@babel/helper-explode-assignable-expression": {
@@ -251,19 +166,6 @@
"dev": true,
"requires": {
"@babel/types": "^7.5.5"
- },
- "dependencies": {
- "@babel/types": {
- "version": "7.6.1",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
- "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
- "dev": true,
- "requires": {
- "esutils": "^2.0.2",
- "lodash": "^4.17.13",
- "to-fast-properties": "^2.0.0"
- }
- }
}
},
"@babel/helper-module-imports": {
@@ -287,19 +189,6 @@
"@babel/template": "^7.4.4",
"@babel/types": "^7.5.5",
"lodash": "^4.17.13"
- },
- "dependencies": {
- "@babel/types": {
- "version": "7.6.1",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
- "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
- "dev": true,
- "requires": {
- "esutils": "^2.0.2",
- "lodash": "^4.17.13",
- "to-fast-properties": "^2.0.0"
- }
- }
}
},
"@babel/helper-optimise-call-expression": {
@@ -349,19 +238,6 @@
"@babel/helper-optimise-call-expression": "^7.0.0",
"@babel/traverse": "^7.5.5",
"@babel/types": "^7.5.5"
- },
- "dependencies": {
- "@babel/types": {
- "version": "7.6.1",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
- "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
- "dev": true,
- "requires": {
- "esutils": "^2.0.2",
- "lodash": "^4.17.13",
- "to-fast-properties": "^2.0.0"
- }
- }
}
},
"@babel/helper-simple-access": {
@@ -404,92 +280,6 @@
"@babel/template": "^7.6.0",
"@babel/traverse": "^7.6.0",
"@babel/types": "^7.6.0"
- },
- "dependencies": {
- "@babel/generator": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.0.tgz",
- "integrity": "sha512-Ms8Mo7YBdMMn1BYuNtKuP/z0TgEIhbcyB8HVR6PPNYp4P61lMsABiS4A3VG1qznjXVCf3r+fVHhm4efTYVsySA==",
- "dev": true,
- "requires": {
- "@babel/types": "^7.6.0",
- "jsesc": "^2.5.1",
- "lodash": "^4.17.13",
- "source-map": "^0.5.0",
- "trim-right": "^1.0.1"
- }
- },
- "@babel/parser": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.0.tgz",
- "integrity": "sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ==",
- "dev": true
- },
- "@babel/template": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz",
- "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==",
- "dev": true,
- "requires": {
- "@babel/code-frame": "^7.0.0",
- "@babel/parser": "^7.6.0",
- "@babel/types": "^7.6.0"
- }
- },
- "@babel/traverse": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.0.tgz",
- "integrity": "sha512-93t52SaOBgml/xY74lsmt7xOR4ufYvhb5c5qiM6lu4J/dWGMAfAh6eKw4PjLes6DI6nQgearoxnFJk60YchpvQ==",
- "dev": true,
- "requires": {
- "@babel/code-frame": "^7.5.5",
- "@babel/generator": "^7.6.0",
- "@babel/helper-function-name": "^7.1.0",
- "@babel/helper-split-export-declaration": "^7.4.4",
- "@babel/parser": "^7.6.0",
- "@babel/types": "^7.6.0",
- "debug": "^4.1.0",
- "globals": "^11.1.0",
- "lodash": "^4.17.13"
- },
- "dependencies": {
- "@babel/code-frame": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
- "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
- "dev": true,
- "requires": {
- "@babel/highlight": "^7.0.0"
- }
- }
- }
- },
- "@babel/types": {
- "version": "7.6.1",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
- "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
- "dev": true,
- "requires": {
- "esutils": "^2.0.2",
- "lodash": "^4.17.13",
- "to-fast-properties": "^2.0.0"
- }
- },
- "debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
- "dev": true,
- "requires": {
- "ms": "^2.1.1"
- }
- },
- "source-map": {
- "version": "0.5.7",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
- "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
- "dev": true
- }
}
},
"@babel/highlight": {
@@ -988,30 +778,25 @@
"invariant": "^2.2.2",
"js-levenshtein": "^1.1.3",
"semver": "^5.5.0"
- },
- "dependencies": {
- "@babel/types": {
- "version": "7.6.1",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
- "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
- "dev": true,
- "requires": {
- "esutils": "^2.0.2",
- "lodash": "^4.17.13",
- "to-fast-properties": "^2.0.0"
- }
- }
}
},
"@babel/template": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz",
- "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==",
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz",
+ "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
- "@babel/parser": "^7.4.4",
- "@babel/types": "^7.4.4"
+ "@babel/parser": "^7.6.0",
+ "@babel/types": "^7.6.0"
+ },
+ "dependencies": {
+ "@babel/parser": {
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.0.tgz",
+ "integrity": "sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ==",
+ "dev": true
+ }
}
},
"@babel/traverse": {
@@ -1031,32 +816,12 @@
"lodash": "^4.17.13"
},
"dependencies": {
- "@babel/code-frame": {
- "version": "7.5.5",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
- "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
- "dev": true,
- "requires": {
- "@babel/highlight": "^7.0.0"
- }
- },
"@babel/parser": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.0.tgz",
"integrity": "sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ==",
"dev": true
},
- "@babel/types": {
- "version": "7.6.1",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
- "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
- "dev": true,
- "requires": {
- "esutils": "^2.0.2",
- "lodash": "^4.17.13",
- "to-fast-properties": "^2.0.0"
- }
- },
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
@@ -1069,13 +834,13 @@
}
},
"@babel/types": {
- "version": "7.5.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.0.tgz",
- "integrity": "sha512-UFpDVqRABKsW01bvw7/wSUe56uy6RXM5+VJibVVAybDGxEW25jdwiFJEf7ASvSaC7sN7rbE/l3cLp2izav+CtQ==",
+ "version": "7.6.1",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz",
+ "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==",
"dev": true,
"requires": {
"esutils": "^2.0.2",
- "lodash": "^4.17.11",
+ "lodash": "^4.17.13",
"to-fast-properties": "^2.0.0"
}
},
@@ -3048,9 +2813,9 @@
}
},
"electron-to-chromium": {
- "version": "1.3.252",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.252.tgz",
- "integrity": "sha512-NWJ5TztDnjExFISZHFwpoJjMbLUifsNBnx7u2JI0gCw6SbKyQYYWWtBHasO/jPtHym69F4EZuTpRNGN11MT/jg==",
+ "version": "1.3.254",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.254.tgz",
+ "integrity": "sha512-7I5/OkgR6JKy6RFLJeru0kc0RMmmMu1UnkHBKInFKRrg1/4EQKIqOaUqITSww/SZ1LqWwp1qc/LLoIGy449eYw==",
"dev": true
},
"elliptic": {
@@ -3178,9 +2943,9 @@
"dev": true
},
"esutils": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
- "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true
},
"eventemitter3": {
@@ -3549,9 +3314,9 @@
}
},
"follow-redirects": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.9.0.tgz",
- "integrity": "sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==",
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.8.1.tgz",
+ "integrity": "sha512-micCIbldHioIegeKs41DoH0KS3AXfFzgS30qVkM6z/XOE/GJgvmsoc839NUqa1B9udYe9dQxgv7KFwng6+p/dw==",
"requires": {
"debug": "^3.0.0"
}
@@ -5651,9 +5416,9 @@
}
},
"minimist": {
- "version": "0.0.10",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
- "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
+ "version": "0.0.8",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
},
"mississippi": {
"version": "3.0.0",
@@ -5719,6 +5484,14 @@
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
},
+ "moment-timezone": {
+ "version": "0.5.26",
+ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.26.tgz",
+ "integrity": "sha512-sFP4cgEKTCymBBKgoxZjYzlSovC20Y6J7y3nanDc5RoBIXKlZhoYwBoZGe3flwU6A372AcRwScH8KiwV6zjy1g==",
+ "requires": {
+ "moment": ">= 2.9.0"
+ }
+ },
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@@ -5782,6 +5555,21 @@
"resolved": "https://registry.npmjs.org/nextcloud-password-confirmation/-/nextcloud-password-confirmation-0.4.2.tgz",
"integrity": "sha512-DZXsfdk3iCsRWtd0lsYM1nqQ/oD9YlQ2WbC4qRZo20enUQLjJWZ8lYhKftXowmYL41t7spThnznJ7ihMG2/vUQ=="
},
+ "nextcloud-router": {
+ "version": "0.0.9",
+ "resolved": "https://registry.npmjs.org/nextcloud-router/-/nextcloud-router-0.0.9.tgz",
+ "integrity": "sha512-w0i4xqFwJJuXNWFf9AB9huCWW5XmwdJHSHa7oXlOLTAvP9WxwU3KCm/mcKy8Eb0cT0ElRPg72HLUxl7oyEWoBQ==",
+ "requires": {
+ "core-js": "^3.1.4"
+ },
+ "dependencies": {
+ "core-js": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz",
+ "integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw=="
+ }
+ }
+ },
"nextcloud-vue": {
"version": "0.12.3",
"resolved": "https://registry.npmjs.org/nextcloud-vue/-/nextcloud-vue-0.12.3.tgz",
@@ -5928,9 +5716,9 @@
}
},
"node-releases": {
- "version": "1.1.29",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.29.tgz",
- "integrity": "sha512-R5bDhzh6I+tpi/9i2hrrvGJ3yKPYzlVOORDkXhnZuwi5D3q1I5w4vYy24PJXTcLk9Q0kws9TO77T75bcK8/ysQ==",
+ "version": "1.1.30",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.30.tgz",
+ "integrity": "sha512-BHcr1g6NeUH12IL+X3Flvs4IOnl1TL0JczUhEZjDE+FXXPQcVCNr8NEPb01zqGxzhTpdyJL5GXemaCW7aw6Khw==",
"dev": true,
"requires": {
"semver": "^5.3.0"
@@ -6436,9 +6224,9 @@
"integrity": "sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA=="
},
"portfinder": {
- "version": "1.0.24",
- "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.24.tgz",
- "integrity": "sha512-ekRl7zD2qxYndYflwiryJwMioBI7LI7rVXg3EnLK3sjkouT5eOuhS3gS255XxBksa30VG8UPZYZCdgfGOfkSUg==",
+ "version": "1.0.23",
+ "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.23.tgz",
+ "integrity": "sha512-B729mL/uLklxtxuiJKfQ84WPxNw5a7Yhx3geQZdcA4GjNjZSTSSMMWyoennMVnTWSmAR0lMdzWYN0JLnHrg1KQ==",
"requires": {
"async": "^1.5.2",
"debug": "^2.2.0",
@@ -9276,7 +9064,7 @@
},
"wrap-ansi": {
"version": "2.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+ "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
"dev": true,
"requires": {
diff --git a/package.json b/package.json
index d02dd259982..022db976dab 100644
--- a/package.json
+++ b/package.json
@@ -41,8 +41,10 @@
"lodash": "^4.17.15",
"marked": "^0.7.0",
"moment": "^2.24.0",
+ "moment-timezone": "^0.5.26",
"nextcloud-axios": "^0.2.1",
"nextcloud-password-confirmation": "^0.4.2",
+ "nextcloud-router": "0.0.9",
"nextcloud-vue": "^0.12.3",
"nextcloud-vue-collections": "^0.5.6",
"query-string": "^5.1.1",