Merge pull request #17506 from nextcloud/bugfix/12790/workflow-frontend
Workflow frontend polishing
This commit is contained in:
commit
64bfd4bbeb
|
@ -25,5 +25,6 @@ return [
|
|||
],
|
||||
'ocs-resources' => [
|
||||
'global_workflows' => ['url' => '/api/v1/workflows/global'],
|
||||
'user_workflows' => ['url' => '/api/v1/workflows/user'],
|
||||
],
|
||||
];
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
.workflowengine {
|
||||
.pull-right {
|
||||
float: right
|
||||
}
|
||||
|
||||
.invalid-input {
|
||||
border-color: var(--color-error);
|
||||
}
|
||||
|
||||
.operation {
|
||||
padding: 5px 5px 20px;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: var(--color-border) 1px solid;
|
||||
border-left: rgba(0, 0, 0, 0) 2px solid;
|
||||
|
||||
&.modified {
|
||||
border-left: var(--color-warning) 2px solid;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
span.info {
|
||||
padding: 7px;
|
||||
color: var(--color-border);
|
||||
}
|
||||
|
||||
.msg {
|
||||
border-radius: 3px;
|
||||
margin: 3px 3px 3px 0;
|
||||
padding: 5px;
|
||||
transition: opacity .5s;
|
||||
}
|
||||
|
||||
.check {
|
||||
padding-left: 5px;
|
||||
&:hover {
|
||||
background-color: var(--color-background-dark);
|
||||
}
|
||||
}
|
||||
|
||||
.button-delete,
|
||||
.button-delete-check {
|
||||
opacity: 0.5;
|
||||
padding: 11px;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rules {
|
||||
.icon-loading-small {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.operation:nth-last-child(2) {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.operation-header {
|
||||
display: flex;
|
||||
margin-left: 5px;
|
||||
|
||||
.operation-name {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
align-self: flex-start;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.select2-container {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.icon-delete {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
#workflowengine .multiselect .multiselect__single {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#workflowengine .option__icon {
|
||||
min-width: 25px;
|
||||
}
|
||||
|
||||
#workflowengine input,
|
||||
#workflowengine .multiselect {
|
||||
width: 100%;
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -79,8 +79,8 @@ class UserWorkflowsController extends AWorkflowController {
|
|||
* @throws OCSBadRequestException
|
||||
* @throws OCSForbiddenException
|
||||
*/
|
||||
public function create(string $class, string $name, array $checks, string $operation): DataResponse {
|
||||
return parent::create($class, $name, $checks, $operation);
|
||||
public function create(string $class, string $name, array $checks, string $operation, string $entity, array $events): DataResponse {
|
||||
return parent::create($class, $name, $checks, $operation, $entity, $events);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -88,8 +88,8 @@ class UserWorkflowsController extends AWorkflowController {
|
|||
* @throws OCSBadRequestException
|
||||
* @throws OCSForbiddenException
|
||||
*/
|
||||
public function update(int $id, string $name, array $checks, string $operation): DataResponse {
|
||||
return parent::update($id, $name, $checks, $operation);
|
||||
public function update(int $id, string $name, array $checks, string $operation, string $entity, array $events): DataResponse {
|
||||
return parent::update($id, $name, $checks, $operation, $entity, $events);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -561,6 +561,8 @@ class Manager implements IManager {
|
|||
|
||||
$operation['checks'][] = $check;
|
||||
}
|
||||
$operation['events'] = json_decode($operation['events'], true);
|
||||
|
||||
|
||||
return $operation;
|
||||
}
|
||||
|
|
|
@ -24,12 +24,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\WorkflowEngine\Service;
|
||||
|
||||
use OCA\WorkflowEngine\AppInfo\Application;
|
||||
use OCA\WorkflowEngine\Entity\File;
|
||||
use OCA\WorkflowEngine\Helper\ScopeContext;
|
||||
use OCA\WorkflowEngine\Manager;
|
||||
use OCP\AppFramework\QueryException;
|
||||
use OCP\Files\Node;
|
||||
use OCP\Files\Storage\IStorage;
|
||||
use OCP\IL10N;
|
||||
use OCP\IServerContainer;
|
||||
|
@ -128,7 +125,7 @@ class RuleMatcher implements IRuleMatcher {
|
|||
list($entity, $subject) = $entityInfo;
|
||||
$checkInstance->setEntitySubject($entity, $subject);
|
||||
}
|
||||
} else {
|
||||
} else if(!$checkInstance instanceof ICheck) {
|
||||
// Check is invalid
|
||||
throw new \UnexpectedValueException($this->l->t('Check %s is invalid or does not exist', $check['class']));
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
<Multiselect v-model="currentOperator"
|
||||
:disabled="!currentOption"
|
||||
:options="operators"
|
||||
class="comparator"
|
||||
label="name"
|
||||
track-by="operator"
|
||||
:allow-empty="false"
|
||||
|
@ -21,6 +22,7 @@
|
|||
v-model="check.value"
|
||||
:disabled="!currentOption"
|
||||
:check="check"
|
||||
class="option"
|
||||
@input="updateCheck"
|
||||
@valid="(valid=true) && validate()"
|
||||
@invalid="(valid=false) && validate()" />
|
||||
|
@ -30,9 +32,10 @@
|
|||
:class="{ invalid: !valid }"
|
||||
:disabled="!currentOption"
|
||||
:placeholder="valuePlaceholder"
|
||||
class="option"
|
||||
@input="updateCheck">
|
||||
<Actions v-if="deleteVisible || !currentOption">
|
||||
<ActionButton icon="icon-delete" @click="$emit('remove')" />
|
||||
<ActionButton icon="icon-close" @click="$emit('remove')" />
|
||||
</Actions>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -73,17 +76,16 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
Checks() {
|
||||
checks() {
|
||||
return this.$store.getters.getChecksForEntity(this.rule.entity)
|
||||
},
|
||||
operators() {
|
||||
if (!this.currentOption) { return [] }
|
||||
return this.Checks[this.currentOption.class].operators
|
||||
return this.checks[this.currentOption.class].operators
|
||||
},
|
||||
currentComponent() {
|
||||
if (!this.currentOption) { return [] }
|
||||
const currentComponent = this.Checks[this.currentOption.class].component
|
||||
return currentComponent
|
||||
return this.checks[this.currentOption.class].component
|
||||
},
|
||||
valuePlaceholder() {
|
||||
if (this.currentOption && this.currentOption.placeholder) {
|
||||
|
@ -98,8 +100,8 @@ export default {
|
|||
}
|
||||
},
|
||||
mounted() {
|
||||
this.options = Object.values(this.Checks)
|
||||
this.currentOption = this.Checks[this.check.class]
|
||||
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: {
|
||||
|
@ -111,13 +113,8 @@ export default {
|
|||
},
|
||||
validate() {
|
||||
if (this.currentOption && this.currentOption.validate) {
|
||||
if (this.currentOption.validate(this.check)) {
|
||||
this.valid = true
|
||||
} else {
|
||||
this.valid = false
|
||||
}
|
||||
this.valid = !!this.currentOption.validate(this.check)
|
||||
}
|
||||
this.$store.dispatch('setValid', { rule: this.rule, valid: this.rule.valid && this.valid })
|
||||
return this.valid
|
||||
},
|
||||
updateCheck() {
|
||||
|
@ -128,7 +125,7 @@ export default {
|
|||
this.check.operator = this.currentOperator.operator
|
||||
|
||||
if (!this.validate()) {
|
||||
return
|
||||
this.check.invalid = !this.valid
|
||||
}
|
||||
this.$emit('update', this.check)
|
||||
}
|
||||
|
@ -142,14 +139,30 @@ export default {
|
|||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
padding-right: 20px;
|
||||
& > *:not(.icon-delete) {
|
||||
& > *:not(.close) {
|
||||
width: 180px;
|
||||
}
|
||||
& > .comparator {
|
||||
min-width: 130px;
|
||||
width: 130px;
|
||||
}
|
||||
& > .option {
|
||||
min-width: 230px;
|
||||
width: 230px;
|
||||
}
|
||||
& > .multiselect,
|
||||
& > input[type=text] {
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.multiselect::v-deep .multiselect__content-wrapper li>span,
|
||||
.multiselect::v-deep .multiselect__single {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
input[type=text] {
|
||||
margin: 0;
|
||||
|
@ -157,14 +170,12 @@ export default {
|
|||
::placeholder {
|
||||
font-size: 10px;
|
||||
}
|
||||
.icon-delete {
|
||||
button.action-item.action-item--single.icon-close {
|
||||
height: 44px;
|
||||
width: 44px;
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -32,17 +32,20 @@
|
|||
:tagging="false"
|
||||
@input="setValue">
|
||||
<template slot="singleLabel" slot-scope="props">
|
||||
<span class="option__icon" :class="props.option.icon" />
|
||||
<span v-if="props.option.icon" class="option__icon" :class="props.option.icon" />
|
||||
<img v-else :src="props.option.iconUrl">
|
||||
<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 v-if="props.option.icon" class="option__icon" :class="props.option.icon" />
|
||||
<img v-else :src="props.option.iconUrl">
|
||||
<span class="option__title">{{ props.option.label }}</span>
|
||||
</template>
|
||||
</Multiselect>
|
||||
<input v-if="!isPredefined"
|
||||
type="text"
|
||||
:value="currentValue.pattern"
|
||||
:placeholder="t('workflowengine', 'e.g. httpd/unix-directory')"
|
||||
@input="updateCustom">
|
||||
</div>
|
||||
</template>
|
||||
|
@ -68,12 +71,12 @@ export default {
|
|||
pattern: '/image\\/.*/'
|
||||
},
|
||||
{
|
||||
icon: 'icon-category-office',
|
||||
iconUrl: OC.imagePath('core', 'filetypes/x-office-document'),
|
||||
label: t('workflowengine', 'Office documents'),
|
||||
pattern: '/(vnd\\.(ms-|openxmlformats-).*))$/'
|
||||
},
|
||||
{
|
||||
icon: 'icon-filetype-file',
|
||||
iconUrl: OC.imagePath('core', 'filetypes/application-pdf'),
|
||||
label: t('workflowengine', 'PDF documents'),
|
||||
pattern: 'application/pdf'
|
||||
}
|
||||
|
@ -130,3 +133,15 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.multiselect, input[type='text'] {
|
||||
width: 100%;
|
||||
}
|
||||
.multiselect >>> .multiselect__content-wrapper li>span,
|
||||
.multiselect >>> .multiselect__single {
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<template>
|
||||
<MultiselectTag v-model="newValue"
|
||||
:multiple="false"
|
||||
label="Select a tag"
|
||||
:label="t('workflowengine', 'Select a tag')"
|
||||
@input="update" />
|
||||
</template>
|
||||
|
||||
|
|
|
@ -56,10 +56,8 @@ export default {
|
|||
required: true
|
||||
},
|
||||
value: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
type: [String, Array],
|
||||
default: null
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
|
@ -67,7 +65,7 @@ export default {
|
|||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
<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"
|
||||
placeholder="e.g. 08:00"
|
||||
@input="update">
|
||||
<input v-model="newValue.endTime"
|
||||
type="text"
|
||||
placeholder="18:00"
|
||||
placeholder="e.g. 18:00"
|
||||
@input="update">
|
||||
<p v-if="!valid" class="invalid-hint">
|
||||
{{ t('workflowengine', 'Please enter a valid time span') }}
|
||||
</p>
|
||||
<Multiselect v-show="valid"
|
||||
v-model="newValue.timezone"
|
||||
:options="timezones"
|
||||
@input="update" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -30,7 +36,7 @@ export default {
|
|||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: '1 MB'
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
@ -46,14 +52,17 @@ export default {
|
|||
},
|
||||
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
|
||||
try {
|
||||
const data = JSON.parse(value)
|
||||
if (data.length === 2) {
|
||||
this.newValue = {
|
||||
startTime: data[0].split(' ', 2)[0],
|
||||
endTime: data[1].split(' ', 2)[0],
|
||||
timezone: data[0].split(' ', 2)[1]
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore invalid values
|
||||
}
|
||||
},
|
||||
validate() {
|
||||
|
@ -86,14 +95,23 @@ export default {
|
|||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.multiselect::v-deep .multiselect__tags:not(:hover):not(:focus):not(:active) {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
width: 50%;
|
||||
margin: 0;
|
||||
margin-bottom: 5px;
|
||||
|
||||
&.timeslot--start {
|
||||
margin-right: 5px;
|
||||
width: calc(50% - 5px);
|
||||
}
|
||||
}
|
||||
|
||||
.invalid-hint {
|
||||
color: var(--color-text-maxcontrast);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -137,3 +137,8 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.multiselect, input[type='text'] {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -27,19 +27,22 @@
|
|||
: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>
|
||||
<!-- v-html can be used here as t() always passes our translated strings though DOMPurify.sanitize -->
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span class="option__title option__title_single" v-html="props.option.label" />
|
||||
</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>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span v-if="props.option.$groupLabel" class="option__title" v-html="props.option.$groupLabel" />
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span v-else class="option__title" v-html="props.option.label" />
|
||||
</template>
|
||||
</Multiselect>
|
||||
<input v-if="!isPredefined"
|
||||
|
@ -65,15 +68,10 @@ export default {
|
|||
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' }
|
||||
]
|
||||
}
|
||||
{ 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' }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -83,8 +81,6 @@ export default {
|
|||
},
|
||||
matchingPredefined() {
|
||||
return this.predefinedTypes
|
||||
.map(groups => groups.children)
|
||||
.flat()
|
||||
.find((type) => this.newValue === type.pattern)
|
||||
},
|
||||
isPredefined() {
|
||||
|
@ -92,14 +88,9 @@ export default {
|
|||
},
|
||||
customValue() {
|
||||
return {
|
||||
label: t('workflowengine', 'Others'),
|
||||
children: [
|
||||
{
|
||||
icon: 'icon-settings-dark',
|
||||
label: t('workflowengine', 'Custom user agent'),
|
||||
pattern: ''
|
||||
}
|
||||
]
|
||||
icon: 'icon-settings-dark',
|
||||
label: t('workflowengine', 'Custom user agent'),
|
||||
pattern: ''
|
||||
}
|
||||
},
|
||||
currentValue() {
|
||||
|
@ -115,8 +106,8 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
validateRegex(string) {
|
||||
var regexRegex = /^\/(.*)\/([gui]{0,3})$/
|
||||
var result = regexRegex.exec(string)
|
||||
const regexRegex = /^\/(.*)\/([gui]{0,3})$/
|
||||
const result = regexRegex.exec(string)
|
||||
return result !== null
|
||||
},
|
||||
setValue(value) {
|
||||
|
@ -133,3 +124,32 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.multiselect, input[type='text'] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.multiselect .multiselect__content-wrapper li>span {
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.multiselect::v-deep .multiselect__single {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.option__icon {
|
||||
display: inline-block;
|
||||
min-width: 30px;
|
||||
background-position: left;
|
||||
}
|
||||
.option__title {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -22,56 +22,91 @@
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<Multiselect v-model="newValue"
|
||||
:class="{'icon-loading-small': groups.length === 0}"
|
||||
<Multiselect :value="currentValue"
|
||||
:loading="status.isLoading && groups.length === 0"
|
||||
:options="groups"
|
||||
:multiple="false"
|
||||
label="displayname"
|
||||
track-by="id"
|
||||
@input="setValue" />
|
||||
@search-change="searchAsync"
|
||||
@input="(value) => $emit('input', value.id)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Multiselect } from 'nextcloud-vue/dist/Components/Multiselect'
|
||||
import valueMixin from '../../mixins/valueMixin'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
||||
const groups = []
|
||||
const status = {
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'RequestUserGroup',
|
||||
components: {
|
||||
Multiselect
|
||||
},
|
||||
mixins: [
|
||||
valueMixin
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
groups: []
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
check: {
|
||||
type: Object,
|
||||
default: () => { return {} }
|
||||
}
|
||||
},
|
||||
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)
|
||||
})
|
||||
data() {
|
||||
return {
|
||||
groups: groups,
|
||||
status: status
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentValue() {
|
||||
return this.groups.find(group => group.id === this.value) || null
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
if (this.groups.length === 0) {
|
||||
await this.searchAsync('')
|
||||
}
|
||||
if (this.currentValue === null) {
|
||||
await this.searchAsync(this.value)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateInternalValue() {
|
||||
this.newValue = this.groups.find(group => group.id === this.value) || null
|
||||
searchAsync(searchQuery) {
|
||||
if (this.status.isLoading) {
|
||||
return
|
||||
}
|
||||
|
||||
this.status.isLoading = true
|
||||
return axios.get(OC.linkToOCS('cloud', 2) + 'groups?limit=20&search=' + encodeURI(searchQuery)).then((response) => {
|
||||
response.data.ocs.data.groups.reduce((obj, item) => {
|
||||
obj.push({
|
||||
id: item,
|
||||
displayname: item
|
||||
})
|
||||
return obj
|
||||
}, []).forEach((group) => this.addGroup(group))
|
||||
this.status.isLoading = false
|
||||
}, (error) => {
|
||||
console.error('Error while loading group list', error.response)
|
||||
})
|
||||
},
|
||||
setValue(value) {
|
||||
if (value !== null) {
|
||||
this.$emit('input', this.newValue.id)
|
||||
addGroup(group) {
|
||||
const index = this.groups.findIndex((item) => item.id === group.id)
|
||||
if (index === -1) {
|
||||
this.groups.push(group)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.multiselect {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -65,7 +65,7 @@ const FileChecks = [
|
|||
{ operator: 'greater', name: t('workflowengine', 'greater') }
|
||||
],
|
||||
placeholder: (check) => '5 MB',
|
||||
validate: (check) => check.value.match(/^[0-9]+[ ]?[kmgt]?b$/i) !== null
|
||||
validate: (check) => check.value ? check.value.match(/^[0-9]+[ ]?[kmgt]?b$/i) !== null : false
|
||||
},
|
||||
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="event">
|
||||
<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>
|
||||
|
@ -7,14 +7,16 @@
|
|||
<Multiselect v-else
|
||||
:value="currentEvent"
|
||||
:options="allEvents"
|
||||
label="eventName"
|
||||
track-by="id"
|
||||
:allow-empty="false"
|
||||
:multiple="true"
|
||||
:auto-limit="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 slot="selection" slot-scope="{ values, search, isOpen }">
|
||||
<div v-if="values.length && !isOpen" class="eventlist">
|
||||
<img class="option__icon" :src="values[0].entity.icon">
|
||||
<span v-for="(value, index) in values" :key="value.id" class="text option__title option__title_single">{{ value.displayName }} <span v-if="index+1 < values.length">, </span></span>
|
||||
</div>
|
||||
</template>
|
||||
<template slot="option" slot-scope="props">
|
||||
<img class="option__icon" :src="props.option.entity.icon">
|
||||
|
@ -49,23 +51,22 @@ export default {
|
|||
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)
|
||||
return this.allEvents.filter(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)
|
||||
updateEvent(events) {
|
||||
this.$set(this.rule, 'events', events.map(event => event.eventName))
|
||||
this.$emit('update', this.rule)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.event {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.isComplex {
|
||||
img {
|
||||
vertical-align: top;
|
||||
|
@ -78,6 +79,11 @@ export default {
|
|||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.multiselect {
|
||||
width: 100%;
|
||||
max-width: 550px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.multiselect::v-deep .multiselect__single {
|
||||
display: flex;
|
||||
}
|
||||
|
@ -86,8 +92,10 @@ export default {
|
|||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.multiselect::v-deep .multiselect__tags .multiselect__single {
|
||||
.multiselect::v-deep .multiselect__tags {
|
||||
background-color: var(--color-main-background) !important;
|
||||
height: auto;
|
||||
min-height: 34px;
|
||||
}
|
||||
|
||||
.multiselect:not(.multiselect--disabled)::v-deep .multiselect__tags .multiselect__single {
|
||||
|
@ -107,4 +115,9 @@ export default {
|
|||
.option__title_single {
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.eventlist img,
|
||||
.eventlist .text {
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -55,6 +55,7 @@ export default {
|
|||
.actions__item_options {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
h3, small {
|
||||
padding: 6px;
|
||||
|
@ -63,7 +64,7 @@ export default {
|
|||
h3 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: 500;
|
||||
font-weight: 600;
|
||||
}
|
||||
small {
|
||||
font-size: 10pt;
|
||||
|
@ -82,6 +83,7 @@ export default {
|
|||
.actions__item__description {
|
||||
padding-top: 5px;
|
||||
text-align: left;
|
||||
width: calc(100% - 105px);
|
||||
small {
|
||||
padding: 0;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="section rule" :style="{ borderLeftColor: operation.color || '' }">
|
||||
<div v-if="operation" class="section rule" :style="{ borderLeftColor: operation.color || '' }">
|
||||
<div class="trigger">
|
||||
<p>
|
||||
<span>{{ t('workflowengine', 'When') }}</span>
|
||||
|
@ -23,28 +23,26 @@
|
|||
</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 class="buttons">
|
||||
<button v-tooltip="ruleStatus.tooltip"
|
||||
class="status-button icon"
|
||||
:class="ruleStatus.class"
|
||||
@click="saveRule">
|
||||
{{ ruleStatus.title }}
|
||||
</button>
|
||||
<button v-if="rule.id < -1" @click="cancelRule">
|
||||
{{ t('workflowengine', 'Cancel') }}
|
||||
</button>
|
||||
<button v-else @click="deleteRule">
|
||||
{{ t('workflowengine', 'Delete') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -85,7 +83,7 @@ export default {
|
|||
return this.$store.getters.getOperationForRule(this.rule)
|
||||
},
|
||||
ruleStatus() {
|
||||
if (this.error || !this.rule.valid) {
|
||||
if (this.error || !this.rule.valid || this.rule.checks.some((check) => check.invalid === true)) {
|
||||
return {
|
||||
title: t('workflowengine', 'The configuration is invalid'),
|
||||
class: 'icon-close-white invalid',
|
||||
|
@ -163,11 +161,18 @@ export default {
|
|||
background-position: 10px center;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: block;
|
||||
button {
|
||||
float: right;
|
||||
height: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
.status-button {
|
||||
transition: 0.5s ease all;
|
||||
display: block;
|
||||
margin: auto;
|
||||
margin-right: 0;
|
||||
margin: 3px 10px 3px auto;
|
||||
}
|
||||
.status-button.primary {
|
||||
padding-left: 32px;
|
||||
|
@ -199,12 +204,6 @@ export default {
|
|||
.action {
|
||||
max-width: 400px;
|
||||
position: relative;
|
||||
.buttons {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
display: flex;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
.icon-confirm {
|
||||
background-position: right 27px;
|
||||
|
@ -238,6 +237,7 @@ export default {
|
|||
margin: 0;
|
||||
width: 180px;
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--color-text-maxcontrast);
|
||||
font-weight: normal;
|
||||
text-align: left;
|
||||
font-size: 1em;
|
||||
|
|
|
@ -22,8 +22,9 @@
|
|||
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
|
||||
const scopeValue = loadState('workflowengine', 'scope') === 0 ? 'global' : 'user'
|
||||
|
||||
const getApiUrl = (url) => {
|
||||
const scopeValue = loadState('workflowengine', 'scope') === 0 ? 'global' : 'user'
|
||||
return OC.linkToOCS('apps/workflowengine/api/v1/workflows', 2) + scopeValue + url + '?format=json'
|
||||
}
|
||||
|
||||
|
|
|
@ -19,23 +19,29 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
const regexRegex = /^\/(.*)\/([gui]{0,3})$/
|
||||
const regexIPv4 = /^(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])$/
|
||||
const regexIPv6 = /^(([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])$/
|
||||
|
||||
const validateRegex = function(string) {
|
||||
var regexRegex = /^\/(.*)\/([gui]{0,3})$/
|
||||
var result = regexRegex.exec(string)
|
||||
return result !== null
|
||||
if (!string) {
|
||||
return false
|
||||
}
|
||||
return regexRegex.exec(string) !== 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
|
||||
if (!string) {
|
||||
return false
|
||||
}
|
||||
return regexIPv4.exec(string) !== 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
|
||||
if (!string) {
|
||||
return false
|
||||
}
|
||||
return regexIPv6.exec(string) !== null
|
||||
}
|
||||
|
||||
const stringValidator = (check) => {
|
||||
|
|
|
@ -71,7 +71,9 @@ const store = new Vuex.Store({
|
|||
plugin = Object.assign(
|
||||
{ color: 'var(--color-primary-element)' },
|
||||
plugin, state.operations[plugin.id] || {})
|
||||
Vue.set(state.operations, plugin.id, plugin)
|
||||
if (typeof state.operations[plugin.id] !== 'undefined') {
|
||||
Vue.set(state.operations, plugin.id, plugin)
|
||||
}
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
*
|
||||
*/
|
||||
use OCA\WorkflowEngine\AppInfo\Application;
|
||||
style(Application::APP_ID, 'multiselect');
|
||||
|
||||
/** @var array $_ */
|
||||
/** @var \OCP\IL10N $l */
|
||||
|
|
|
@ -24,9 +24,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCP\WorkflowEngine;
|
||||
|
||||
|
||||
use OCP\Files\Storage\IStorage;
|
||||
|
||||
/**
|
||||
* Interface IFileCheck
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue