Unified workflow management
Signed-off-by: Julius Härtl <jus@bitgrid.net>
This commit is contained in:
parent
9eb7a31864
commit
ad976c66fd
14 changed files with 926 additions and 3 deletions
|
@ -382,4 +382,4 @@ import OperationsTemplate from './templates/operations.handlebars';
|
|||
this.collection.each(this.renderOperation, this);
|
||||
}
|
||||
});
|
||||
})();
|
||||
})();
|
104
apps/workflowengine/src/components/Check.vue
Normal file
104
apps/workflowengine/src/components/Check.vue
Normal file
|
@ -0,0 +1,104 @@
|
|||
<template>
|
||||
<div class="check" @click="showDelete" v-click-outside="hideDelete">
|
||||
<Multiselect v-model="currentOption" :options="options" label="name"
|
||||
track-by="class" :allow-empty="false" :placeholder="t('workflowengine', 'Select a filter')"
|
||||
@input="updateCheck" ref="checkSelector"></Multiselect>
|
||||
<Multiselect v-if="currentOption" v-model="currentOperator" @input="updateCheck"
|
||||
:options="operators" label="name" track-by="operator"
|
||||
:allow-empty="false" :placeholder="t('workflowengine', 'Select a comparator')"></Multiselect>
|
||||
<component v-if="currentOperator && currentComponent" :is="currentOption.component()" v-model="check.value" />
|
||||
<input v-else-if="currentOperator" type="text" v-model="check.value" @input="updateCheck" />
|
||||
<Actions>
|
||||
<ActionButton icon="icon-delete" v-if="deleteVisible || !currentOption" @click="$emit('remove')" />
|
||||
</Actions>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Multiselect, Actions, ActionButton } from 'nextcloud-vue'
|
||||
import ClickOutside from 'vue-click-outside';
|
||||
|
||||
export default {
|
||||
name: 'Check',
|
||||
components: {
|
||||
ActionButton,
|
||||
Actions,
|
||||
Multiselect
|
||||
},
|
||||
directives: {
|
||||
ClickOutside
|
||||
},
|
||||
props: {
|
||||
check: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
deleteVisible: false,
|
||||
currentOption: null,
|
||||
currentOperator: null,
|
||||
options: [],
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.options = Object.values(OCA.WorkflowEngine.Plugins).map((plugin) => {
|
||||
if (plugin.component) {
|
||||
return {...plugin.getCheck(), component: plugin.component}
|
||||
}
|
||||
return plugin.getCheck()
|
||||
})
|
||||
this.currentOption = this.options.find((option) => option.class === this.check.class)
|
||||
this.currentOperator = this.operators.find((operator) => operator.operator === this.check.operator)
|
||||
this.$nextTick(() => {
|
||||
this.$refs.checkSelector.$el.focus()
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
operators() {
|
||||
if (!this.currentOption)
|
||||
return []
|
||||
return this.options.find((item) => item.class === this.currentOption.class).operators
|
||||
},
|
||||
currentComponent() {
|
||||
if (!this.currentOption)
|
||||
return []
|
||||
let currentComponent = this.options.find((item) => item.class === this.currentOption.class).component
|
||||
return currentComponent && currentComponent()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showDelete() {
|
||||
this.deleteVisible = true
|
||||
},
|
||||
hideDelete() {
|
||||
this.deleteVisible = false
|
||||
},
|
||||
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
|
||||
this.$emit('update', this.check)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.check {
|
||||
display: flex;
|
||||
& > .multiselect,
|
||||
& > input[type=text] {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
input[type=text] {
|
||||
margin: 0;
|
||||
}
|
||||
::placeholder {
|
||||
font-size: 10px;
|
||||
}
|
||||
</style>
|
84
apps/workflowengine/src/components/Event.vue
Normal file
84
apps/workflowengine/src/components/Event.vue
Normal file
|
@ -0,0 +1,84 @@
|
|||
<template>
|
||||
<div>
|
||||
<Multiselect :value="currentEvent" :options="allEvents" label="name" track-by="id" :allow-empty="false" :disabled="allEvents.length <= 1" @input="updateEvent">
|
||||
<template slot="singleLabel" slot-scope="props">
|
||||
<span class="option__icon" :class="props.option.icon"></span>
|
||||
<span class="option__title option__title_single">{{ props.option.name }}</span>
|
||||
</template>
|
||||
<template slot="option" slot-scope="props">
|
||||
<span class="option__icon" :class="props.option.icon"></span>
|
||||
<span class="option__title">{{ props.option.name }}</span>
|
||||
</template>
|
||||
</Multiselect>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Multiselect } from 'nextcloud-vue'
|
||||
import { eventService, operationService } from '../services/Operation'
|
||||
|
||||
export default {
|
||||
name: "Event",
|
||||
components: {
|
||||
Multiselect
|
||||
},
|
||||
props: {
|
||||
rule: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentEvent() {
|
||||
if (typeof this.rule.event === 'undefined') {
|
||||
return this.allEvents.length > 0 ? this.allEvents[0] : null
|
||||
}
|
||||
return this.allEvents.find(event => event.id === this.rule.event)
|
||||
},
|
||||
allEvents() {
|
||||
return this.operation.events.map((eventName) => eventService.get(eventName))
|
||||
},
|
||||
operation() {
|
||||
return operationService.get(this.rule.class)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateEvent(event) {
|
||||
this.$set(this.rule, 'event', event.id)
|
||||
this.$emit('update', this.rule)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.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>
|
83
apps/workflowengine/src/components/Operation.vue
Normal file
83
apps/workflowengine/src/components/Operation.vue
Normal file
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<div class="actions__item" :class="{'colored': !!color}" :style="{ backgroundColor: color }">
|
||||
<div class="icon" :class="icon"></div>
|
||||
<h3>{{ title }}</h3>
|
||||
<small>{{ description }}</small>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Operation",
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.actions__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
margin-left: -1px;
|
||||
padding: 10px;
|
||||
border-radius: var(--border-radius-large);
|
||||
max-width: 230px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.icon {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background-size: 50px 50px;
|
||||
background-position: center center;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
h3, small {
|
||||
padding: 6px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
h3 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
small {
|
||||
font-size: 10pt;
|
||||
}
|
||||
.icon-block {
|
||||
background-image: url(/apps-extra/files_accesscontrol/img/app-dark.svg);
|
||||
}
|
||||
|
||||
.icon-convert-pdf {
|
||||
background-image: url(/apps-extra/workflow_pdf_converter/img/app-dark.svg);
|
||||
}
|
||||
|
||||
.colored {
|
||||
.icon {
|
||||
filter: invert(1);
|
||||
}
|
||||
* {
|
||||
color: var(--color-primary-text)
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,45 @@
|
|||
<template>
|
||||
<multiselect :options="options" track-by="id" label="text" v-model="value"></multiselect>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const pdfConvertOptions = [
|
||||
{
|
||||
id: 'keep;preserve',
|
||||
text: t('workflow_pdf_converter', 'Keep original, preserve existing PDFs'),
|
||||
},
|
||||
{
|
||||
id: 'keep;overwrite',
|
||||
text: t('workflow_pdf_converter', 'Keep original, overwrite existing PDF'),
|
||||
},
|
||||
{
|
||||
id: 'delete;preserve',
|
||||
text: t('workflow_pdf_converter', 'Delete original, preserve existing PDFs'),
|
||||
},
|
||||
{
|
||||
id: 'delete;overwrite',
|
||||
text: t('workflow_pdf_converter', 'Delete original, overwrite existing PDF'),
|
||||
},
|
||||
]
|
||||
|
||||
import { Multiselect } from 'nextcloud-vue'
|
||||
export default {
|
||||
name: "ConvertToPdf",
|
||||
components: {Multiselect},
|
||||
data() {
|
||||
return {
|
||||
options: pdfConvertOptions,
|
||||
value: pdfConvertOptions[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.multiselect {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
29
apps/workflowengine/src/components/Operations/Tag.vue
Normal file
29
apps/workflowengine/src/components/Operations/Tag.vue
Normal file
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<multiselect :options="options" v-model="value" track-by="id" label="title" :multiple="true" :tagging="true" @input="$emit('input', value.map(item => item.id))"></multiselect>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// TODO: fetch tags from endpoint
|
||||
const tags = [{id: 3, title: 'foo'}, {id: 4, title: 'bar'}]
|
||||
|
||||
import { Multiselect } from 'nextcloud-vue'
|
||||
export default {
|
||||
name: "Tag",
|
||||
components: {Multiselect},
|
||||
data() {
|
||||
return {
|
||||
options: tags,
|
||||
value: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.multiselect {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
209
apps/workflowengine/src/components/Rule.vue
Normal file
209
apps/workflowengine/src/components/Rule.vue
Normal file
|
@ -0,0 +1,209 @@
|
|||
<template>
|
||||
<div class="section rule">
|
||||
<!-- TODO: icon-confirm -->
|
||||
<div class="trigger icon-confirm">
|
||||
<p>
|
||||
<span>{{ t('workflowengine', 'When') }}</span>
|
||||
<Event :rule="rule" @update="updateRule"></Event>
|
||||
</p>
|
||||
<p v-for="check in rule.checks">
|
||||
<span>{{ t('workflowengine', 'and') }}</span>
|
||||
<Check :check="check" @update="updateRule" @remove="removeCheck(check)"></Check>
|
||||
</p>
|
||||
<p>
|
||||
<span> </span>
|
||||
<input v-if="lastCheckComplete" type="button" class="check--add" @click="rule.checks.push({class: null, operator: null, value: null})" value="Add a new filter"/>
|
||||
</p>
|
||||
</div>
|
||||
<div class="action">
|
||||
<div class="buttons">
|
||||
<button class="status-button icon" :class="ruleStatus.class" v-tooltip="ruleStatus.tooltip" @click="saveRule">{{ ruleStatus.title }}</button>
|
||||
<Actions>
|
||||
<ActionButton v-if="rule.id === -1" icon="icon-close" @click="$emit('cancel')">Cancel</ActionButton>
|
||||
<ActionButton v-else icon="icon-delete" @click="deleteRule">Delete</ActionButton>
|
||||
</Actions>
|
||||
</div>
|
||||
<Operation :icon="operation.icon" :title="operation.title" :description="operation.description">
|
||||
<component v-if="operation.options" :is="operation.options" v-model="operation.operation" @input="updateOperation" />
|
||||
</Operation>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Actions, ActionButton, Tooltip } from 'nextcloud-vue'
|
||||
import Event from './Event'
|
||||
import Check from './Check'
|
||||
import Operation from './Operation'
|
||||
import { operationService } from '../services/Operation'
|
||||
import axios from 'nextcloud-axios'
|
||||
import confirmPassword from 'nextcloud-password-confirmation'
|
||||
|
||||
export default {
|
||||
name: 'Rule',
|
||||
components: {
|
||||
Operation, Check, Event, Actions, ActionButton
|
||||
},
|
||||
directives: {
|
||||
Tooltip
|
||||
},
|
||||
props: {
|
||||
rule: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
editing: false,
|
||||
operationService,
|
||||
checks: [],
|
||||
error: null,
|
||||
dirty: this.rule.id === -1,
|
||||
checking: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
operation() {
|
||||
return this.operationService.get(this.rule.class)
|
||||
},
|
||||
ruleStatus() {
|
||||
if (this.error) {
|
||||
return { title: 'Invalid', class: 'icon-close-white invalid', tooltip: { placement: 'bottom', show: true, content: escapeHTML(this.error.data) } }
|
||||
}
|
||||
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: {
|
||||
updateOperation(operation) {
|
||||
this.$set(this.rule, 'operation', operation)
|
||||
},
|
||||
async updateRule() {
|
||||
this.checking = true
|
||||
if (!this.dirty) {
|
||||
this.dirty = true
|
||||
}
|
||||
try {
|
||||
let result = await axios.post(OC.generateUrl(`/apps/workflowengine/operations/test`), this.rule)
|
||||
this.error = null
|
||||
this.checking = false
|
||||
} catch (e) {
|
||||
console.error('Failed to update operation')
|
||||
this.error = e.response
|
||||
this.checking = false
|
||||
}
|
||||
},
|
||||
async saveRule() {
|
||||
try {
|
||||
await confirmPassword()
|
||||
let result
|
||||
if (this.rule.id === -1) {
|
||||
result = await axios.post(OC.generateUrl(`/apps/workflowengine/operations`), this.rule)
|
||||
this.rule.id = result.id
|
||||
} else {
|
||||
result = await axios.put(OC.generateUrl(`/apps/workflowengine/operations/${this.rule.id}`), this.rule)
|
||||
}
|
||||
this.$emit('update', result.data)
|
||||
this.dirty = false
|
||||
this.error = null
|
||||
} catch (e) {
|
||||
console.error('Failed to update operation')
|
||||
this.error = e.response
|
||||
}
|
||||
},
|
||||
async deleteRule() {
|
||||
try {
|
||||
await confirmPassword()
|
||||
await axios.delete(OC.generateUrl(`/apps/workflowengine/operations/${this.rule.id}`))
|
||||
this.$emit('delete')
|
||||
} catch (e) {
|
||||
console.error('Failed to delete operation')
|
||||
this.error = e.response
|
||||
}
|
||||
},
|
||||
removeCheck(check) {
|
||||
const index = this.rule.checks.findIndex(item => item === check)
|
||||
if (index !== -1) {
|
||||
this.rule.checks.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
button.icon {
|
||||
padding-left: 32px;
|
||||
background-position: 10px center;
|
||||
}
|
||||
|
||||
.status-button {
|
||||
transition: 0.5s ease all;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
|
||||
.rule {
|
||||
display: flex;
|
||||
.trigger, .action {
|
||||
flex-grow: 1;
|
||||
min-height: 100px;
|
||||
width: 50%;
|
||||
}
|
||||
.action {
|
||||
position: relative;
|
||||
.buttons {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
.icon-confirm {
|
||||
background-position: right center;
|
||||
}
|
||||
}
|
||||
.trigger p, .action p {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
|
||||
& > span {
|
||||
min-width: 50px;
|
||||
text-align: right;
|
||||
color: var(--color-text-light);
|
||||
padding-right: 5px;
|
||||
}
|
||||
.multiselect {
|
||||
flex-grow: 1;
|
||||
max-width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.check--add {
|
||||
background-position: 7px center;
|
||||
background-color: transparent;
|
||||
padding-left: 6px;
|
||||
width: 160px;
|
||||
border-radius: var(--border-radius);
|
||||
font-weight: normal;
|
||||
text-align: left;
|
||||
font-size: 1em;
|
||||
}
|
||||
</style>
|
39
apps/workflowengine/src/components/Values/FileMimeType.vue
Normal file
39
apps/workflowengine/src/components/Values/FileMimeType.vue
Normal file
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<input type="text" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Multiselect } from 'nextcloud-vue'
|
||||
|
||||
export default {
|
||||
name: 'SizeValue',
|
||||
components: {
|
||||
Multiselect
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
predefinedTypes: [
|
||||
{
|
||||
icon: 'icon-picture',
|
||||
label: 'Images',
|
||||
pattern: '/image\\/.*/'
|
||||
},
|
||||
{
|
||||
icon: 'icon-category-office',
|
||||
label: 'Office documents',
|
||||
pattern: '/(vnd\\.(ms-|openxmlformats-).*))$/'
|
||||
},
|
||||
{
|
||||
icon: 'icon-filetype-file',
|
||||
label: 'PDF documents',
|
||||
pattern: 'application/pdf'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
28
apps/workflowengine/src/components/Values/SizeValue.vue
Normal file
28
apps/workflowengine/src/components/Values/SizeValue.vue
Normal file
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<input type="text" placeholder="1 MB" @input="$emit('input', newValue)" v-model="newValue">
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "SizeValue",
|
||||
props: {
|
||||
value: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
newValue: this.value
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value() {
|
||||
this.newValue = this.value
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
146
apps/workflowengine/src/components/Workflow.vue
Normal file
146
apps/workflowengine/src/components/Workflow.vue
Normal file
|
@ -0,0 +1,146 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="section">
|
||||
<h2>{{ t('workflowengine', 'Workflows') }}</h2>
|
||||
|
||||
<div class="actions" v-if="!hasUnsavedRule">
|
||||
<Operation v-for="operation in getMainOperations" :key="operation.class"
|
||||
:icon="operation.icon" :title="operation.title" :description="operation.description" :color="operation.color"
|
||||
@click.native="createNewRule(operation)"></Operation>
|
||||
</div>
|
||||
<div class="actions__more" v-if="!hasUnsavedRule && hasMoreOperations">
|
||||
<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>
|
||||
<transition name="slide">
|
||||
<div class="actions" v-if="!hasUnsavedRule && showMoreOperations && hasMoreOperations">
|
||||
<Operation v-for="operation in getMoreOperations" :key="operation.class" :icon="operation.icon" :title="operation.title" :description="operation.description"
|
||||
@click.native="createNewRule(operation)"></Operation>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<transition-group name="slide">
|
||||
<Rule v-for="rule in rules" :key="rule.id" :rule="rule" @delete="removeRule(rule)" @cancel="removeRule(rule)" @update="updateRule"></Rule>
|
||||
</transition-group>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Rule from './Rule'
|
||||
import Operation from './Operation'
|
||||
import { operationService } from '../services/Operation'
|
||||
import axios from 'nextcloud-axios'
|
||||
|
||||
const ACTION_LIMIT = 3
|
||||
|
||||
export default {
|
||||
name: 'Workflow',
|
||||
components: {
|
||||
Operation,
|
||||
Rule
|
||||
},
|
||||
async mounted() {
|
||||
const { data } = await axios.get(OC.generateUrl('/apps/workflowengine/operations'))
|
||||
this.rules = data
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
operations: operationService,
|
||||
showMoreOperations: false,
|
||||
rules: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasMoreOperations() {
|
||||
return Object.keys(this.operations.getAll()).length > ACTION_LIMIT
|
||||
},
|
||||
getMainOperations() {
|
||||
return Object.values(this.operations.getAll()).slice(0, ACTION_LIMIT)
|
||||
},
|
||||
getMoreOperations() {
|
||||
return Object.values(this.operations.getAll()).slice(ACTION_LIMIT)
|
||||
|
||||
},
|
||||
hasUnsavedRule() {
|
||||
return !!this.rules.find((rule) => rule.id === -1)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateRule(rule) {
|
||||
let index = this.rules.findIndex((item) => rule === item)
|
||||
this.$set(this.rules, index, rule)
|
||||
},
|
||||
removeRule(rule) {
|
||||
let index = this.rules.findIndex((item) => rule === item)
|
||||
this.rules.splice(index, 1)
|
||||
},
|
||||
showAllOperations() {
|
||||
|
||||
},
|
||||
createNewRule(operation) {
|
||||
this.rules.unshift({
|
||||
id: -1,
|
||||
class: operation.class,
|
||||
name: '', // unused in the new ui, there for legacy reasons
|
||||
checks: [],
|
||||
operation: operation.operation || ''
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.actions {
|
||||
display: flex;
|
||||
.action__item {
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
}
|
||||
|
||||
.actions__more {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
</style>
|
|
@ -17,6 +17,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import FileMimeType from './components/Values/FileMimeType'
|
||||
|
||||
(function() {
|
||||
|
||||
|
@ -65,6 +66,10 @@
|
|||
var regexRegex = /^\/(.*)\/([gui]{0,3})$/,
|
||||
result = regexRegex.exec(string);
|
||||
return result !== null;
|
||||
},
|
||||
|
||||
component: function () {
|
||||
return FileMimeType
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import SizeValue from './components/Values/SizeValue'
|
||||
(function() {
|
||||
|
||||
OCA.WorkflowEngine = OCA.WorkflowEngine || {};
|
||||
|
@ -49,6 +49,9 @@
|
|||
.tooltip({
|
||||
placement: 'bottom'
|
||||
});
|
||||
},
|
||||
component: function () {
|
||||
return SizeValue
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
|
141
apps/workflowengine/src/services/Operation.js
Normal file
141
apps/workflowengine/src/services/Operation.js
Normal file
|
@ -0,0 +1,141 @@
|
|||
import ConvertToPdf from './../components/Operations/ConvertToPdf'
|
||||
import Tag from './../components/Operations/Tag'
|
||||
class OperationService {
|
||||
|
||||
constructor() {
|
||||
this.operations = {}
|
||||
}
|
||||
registerOperation (operation) {
|
||||
this.operations[operation.class] = Object.assign({
|
||||
color: 'var(--color-primary)'
|
||||
}, operation)
|
||||
}
|
||||
|
||||
getAll() {
|
||||
return this.operations
|
||||
}
|
||||
|
||||
get(className) {
|
||||
return this.operations[className]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class EventService {
|
||||
|
||||
constructor() {
|
||||
this.events = {}
|
||||
}
|
||||
registerEvent(event) {
|
||||
this.events[event.id] = event
|
||||
}
|
||||
|
||||
getAll() {
|
||||
return this.events
|
||||
}
|
||||
|
||||
get(id) {
|
||||
return this.events[id]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const operationService = new OperationService()
|
||||
const eventService = new EventService()
|
||||
|
||||
|
||||
const ALL_CHECKS = [
|
||||
'OCA\\WorkflowEngine\\Check\\FileMimeType',
|
||||
'OCA\\WorkflowEngine\\Check\\FileName',
|
||||
'OCA\\WorkflowEngine\\Check\\FileSize',
|
||||
'OCA\\WorkflowEngine\\Check\\FileSystemTags',
|
||||
'OCA\\WorkflowEngine\\Check\\RequestRemoteAddress',
|
||||
'OCA\\WorkflowEngine\\Check\\RequestTime',
|
||||
'OCA\\WorkflowEngine\\Check\\RequestURL',
|
||||
'OCA\\WorkflowEngine\\Check\\RequestUserAgent',
|
||||
'OCA\\WorkflowEngine\\Check\\UserGroupMembership'
|
||||
]
|
||||
|
||||
/**
|
||||
* TODO: move to separate apps
|
||||
* TODO: fetch from initial state api
|
||||
**/
|
||||
const EVENT_FILE_ACCESS = 'EVENT_FILE_ACCESS'
|
||||
const EVENT_FILE_CHANGED = 'EVENT_FILE_CHANGED'
|
||||
const EVENT_FILE_TAGGED = 'EVENT_FILE_TAGGED'
|
||||
|
||||
eventService.registerEvent({
|
||||
id: EVENT_FILE_ACCESS,
|
||||
name: 'File is accessed',
|
||||
icon: 'icon-desktop',
|
||||
checks: ALL_CHECKS,
|
||||
})
|
||||
|
||||
eventService.registerEvent({
|
||||
id: EVENT_FILE_CHANGED,
|
||||
name: 'File was updated',
|
||||
icon: 'icon-folder',
|
||||
checks: ALL_CHECKS,
|
||||
})
|
||||
|
||||
|
||||
eventService.registerEvent({
|
||||
id: EVENT_FILE_TAGGED,
|
||||
name: 'File was tagged',
|
||||
icon: 'icon-tag',
|
||||
checks: ALL_CHECKS,
|
||||
})
|
||||
|
||||
operationService.registerOperation({
|
||||
class: 'OCA\\FilesAccessControl\\Operation',
|
||||
title: 'Block access',
|
||||
description: 'todo',
|
||||
icon: 'icon-block',
|
||||
color: 'var(--color-error)',
|
||||
events: [
|
||||
EVENT_FILE_ACCESS
|
||||
],
|
||||
operation: 'deny'
|
||||
})
|
||||
|
||||
operationService.registerOperation({
|
||||
class: 'OCA\\FilesAutomatedTagging\\Operation',
|
||||
title: 'Tag a file',
|
||||
description: 'todo',
|
||||
icon: 'icon-tag',
|
||||
events: [
|
||||
EVENT_FILE_CHANGED,
|
||||
EVENT_FILE_TAGGED
|
||||
],
|
||||
options: Tag
|
||||
|
||||
})
|
||||
|
||||
operationService.registerOperation({
|
||||
class: 'OCA\\WorkflowPDFConverter\\Operation',
|
||||
title: 'Convert to PDF',
|
||||
description: 'todo',
|
||||
color: '#dc5047',
|
||||
icon: 'icon-convert-pdf',
|
||||
events: [
|
||||
EVENT_FILE_CHANGED,
|
||||
//EVENT_FILE_TAGGED
|
||||
],
|
||||
options: ConvertToPdf
|
||||
})
|
||||
|
||||
|
||||
const legacyChecks = Object.values(OCA.WorkflowEngine.Plugins).map((plugin) => {
|
||||
if (plugin.component) {
|
||||
return {...plugin.getCheck(), component: plugin.component}
|
||||
}
|
||||
return plugin.getCheck()
|
||||
}).reduce((obj, item) => {
|
||||
obj[item.class] = item
|
||||
return obj
|
||||
}, {})
|
||||
|
||||
export {
|
||||
eventService,
|
||||
operationService
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
import './admin'
|
||||
import './filemimetypeplugin'
|
||||
import './filenameplugin'
|
||||
import './filesizeplugin'
|
||||
|
@ -10,3 +9,11 @@ import './requestuseragentplugin'
|
|||
import './usergroupmembershipplugin'
|
||||
|
||||
window.OCA.WorkflowEngine = OCA.WorkflowEngine
|
||||
|
||||
import Vue from 'vue';
|
||||
|
||||
Vue.prototype.t = t;
|
||||
|
||||
import Settings from './components/Workflow';
|
||||
const View = Vue.extend(Settings)
|
||||
new View({}).$mount('#workflowengine')
|
Loading…
Reference in a new issue