Workflow frontend overhaultags/v18.0.0beta1
@@ -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); |
@@ -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); | |||
} | |||
}); | |||
})(); |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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 | |||
} |
@@ -0,0 +1,4 @@ | |||
import MultiselectTag from './MultiselectTag' | |||
export default MultiselectTag | |||
export { MultiselectTag } |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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 |
@@ -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] |
@@ -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 |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -0,0 +1,11 @@ | |||
.multiselect::v-deep .multiselect__single { | |||
display: flex; | |||
} | |||
.option__icon { | |||
min-width: 25px; | |||
} | |||
input, .multiselect { | |||
width: 100%; | |||
} |
@@ -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); |
@@ -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); |
@@ -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); |
@@ -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); |
@@ -1,7 +0,0 @@ | |||
module.exports = function(classname) { | |||
var check = OCA.WorkflowEngine.getCheckByClass(classname); | |||
if (!_.isUndefined(check)) { | |||
return check['operators']; | |||
} | |||
return []; | |||
} |
@@ -1,7 +0,0 @@ | |||
module.exports = function(currentValue, itemValue) { | |||
if (currentValue === itemValue) { | |||
return 'selected="selected"'; | |||
} | |||
return ""; | |||
} |
@@ -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 | |||
} |
@@ -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 } |
@@ -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 |
@@ -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); |
@@ -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); |
@@ -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); |
@@ -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); |
@@ -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 |
@@ -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> |
@@ -1,2 +0,0 @@ | |||
<div class="operations"></div> | |||
<button class="button-add-operation">{{addRuleGroupTXT}}</button> |
@@ -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); |
@@ -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') |
@@ -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> |
@@ -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'), | |||
} | |||
} | |||
] | |||
} | |||
} |
@@ -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": { |
@@ -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", |