Commit 861cd3fb authored by Anton Koch's avatar Anton Koch
Browse files

Merge branch 'files' into 'release_2_4'

Add Assets management UI

See merge request !136
parents 9bc01a19 75cea4c4
Pipeline #79591 passed with stage
in 4 minutes and 2 seconds
......@@ -10,7 +10,14 @@ and this project adheres to
## [Unreleased]
No changes.
### Added
- `media/create` allows uploading asset files
### Updated
- Updated [mbjs-media](https://gitlab.rlp.net/motionbank/mbjs/media)
to 0.6.1
## [2.3.1] - 2020-09-30
......
......@@ -2356,9 +2356,9 @@
"dev": true
},
"@types/node": {
"version": "12.6.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.8.tgz",
"integrity": "sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg=="
"version": "14.11.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.5.tgz",
"integrity": "sha512-jVFzDV6NTbrLMxm4xDSIW/gKnk8rQLF9wAzLWIOg+5nU6ACrIMndeBdXci0FGtqJbP9tQvm6V39eshc96TO2wQ=="
},
"@types/q": {
"version": "1.5.2",
......@@ -3151,9 +3151,9 @@
"dev": true
},
"async": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/async/-/async-3.1.0.tgz",
"integrity": "sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ=="
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz",
"integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw=="
},
"async-each": {
"version": "1.0.3",
......@@ -11096,12 +11096,13 @@
}
},
"mbjs-media": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/mbjs-media/-/mbjs-media-0.2.0.tgz",
"integrity": "sha512-doaXul3eRMryklE5Ub4WrbgecQx4VUxDZOJHdxHS8xXzDPFyZO9EOepI33B2p633cIuLE/JUhRvqUUX7R/IicA==",
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/mbjs-media/-/mbjs-media-0.6.1.tgz",
"integrity": "sha512-Y+cwkAw49E1Ni75DwFr8Aep46mblXEii1VfH6fVitiJpso6Nzxp6HHlEIO0gjwrco7EuyHc0wnJaGKwgbP0cAQ==",
"requires": {
"axios": "^0.18.0",
"bluebird": "^3.5.1",
"debug": "^4.1.1",
"fluent-ffmpeg": "^2.1.2",
"fs-extra": "^6.0.1",
"gm": "^1.23.1",
......@@ -11109,7 +11110,7 @@
"mbjs-data-models": "0.0.8",
"mbjs-utils": "0.0.6",
"md5-file": "^4.0.0",
"mime-types": "^2.1.19",
"mime-types": "^2.1.27",
"mkdirp": "^0.5.1",
"mz": "^2.7.0",
"open-graph-scraper": "^3.5.0",
......@@ -11125,11 +11126,31 @@
"is-buffer": "^2.0.2"
}
},
"debug": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
"requires": {
"ms": "2.1.2"
}
},
"is-buffer": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
"integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A=="
},
"jsonld": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/jsonld/-/jsonld-1.8.1.tgz",
"integrity": "sha512-f0rusl5v8aPKS3jApT5fhYsdTC/JpyK1PoJ+ZtYYtZXoyb1J0Z///mJqLwrfL/g4NueFSqPymDYIi1CcSk7b8Q==",
"requires": {
"canonicalize": "^1.0.1",
"rdf-canonize": "^1.0.2",
"request": "^2.88.0",
"semver": "^5.6.0",
"xmldom": "0.1.19"
}
},
"mbjs-data-models": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/mbjs-data-models/-/mbjs-data-models-0.0.8.tgz",
......@@ -11145,18 +11166,6 @@
"validator": "^9.4.1"
},
"dependencies": {
"jsonld": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/jsonld/-/jsonld-1.8.1.tgz",
"integrity": "sha512-f0rusl5v8aPKS3jApT5fhYsdTC/JpyK1PoJ+ZtYYtZXoyb1J0Z///mJqLwrfL/g4NueFSqPymDYIi1CcSk7b8Q==",
"requires": {
"canonicalize": "^1.0.1",
"rdf-canonize": "^1.0.2",
"request": "^2.88.0",
"semver": "^5.6.0",
"xmldom": "0.1.19"
}
},
"mbjs-utils": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/mbjs-utils/-/mbjs-utils-0.0.4.tgz",
......@@ -11170,6 +11179,24 @@
}
}
}
},
"mime-db": {
"version": "1.44.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
"integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg=="
},
"mime-types": {
"version": "2.1.27",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
"integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
"requires": {
"mime-db": "1.44.0"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
......@@ -13486,10 +13513,9 @@
"dev": true
},
"pretty-bytes": {
"version": "4.0.2",
"resolved": "http://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz",
"integrity": "sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=",
"dev": true
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.4.1.tgz",
"integrity": "sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA=="
},
"pretty-error": {
"version": "2.1.1",
......@@ -18230,6 +18256,12 @@
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
},
"pretty-bytes": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz",
"integrity": "sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=",
"dev": true
}
}
},
......
......@@ -160,6 +160,7 @@ module.exports = function (ctx) {
UI_HIDE_MOSYS: JSON.stringify(process.env.UI_HIDE_MOSYS || false),
UI_HIDE_DOCUMENTS: JSON.stringify(process.env.UI_HIDE_DOCUMENTS || false),
UI_HIDE_GROUPS: JSON.stringify(process.env.UI_HIDE_GROUPS || false),
UI_SHOW_ASSETS: JSON.stringify(process.env.UI_SHOW_ASSETS || false),
MODULE_PROVIDER: JSON.stringify(process.env.MODULE_PROVIDER || null)
}
},
......
Subproject commit eab6a2726b623649f306b47588c884e029ce7898
Subproject commit fa1a09dab1f4066aef8229a494f3937d505878e0
Subproject commit 2ad1d1328d6418dd2bf664ee04030bdb82fa30db
Subproject commit 2cfa4f24b0e38d70ce2035b89869630cf4928011
......@@ -16,7 +16,6 @@ export default {
back: 'Back',
create_account: 'Create Account',
close_account: 'Close Account',
create_document: 'Add Document',
create_timeline: 'Create Timeline',
create_grid: 'Create Grid',
create_package: 'Create Package',
......@@ -95,7 +94,6 @@ export default {
errors: {
no_invitations: 'You didn\'t invite anyone to this group yet.',
no_members: 'This group has no members yet.',
document_delete_failed: 'Failed to delete Document',
item_exists: 'This item already exists.',
unknown: 'Unknown Error',
invalid_email: 'Please enter a valid email',
......@@ -114,6 +112,7 @@ export default {
export_archive_failed: 'Failed to export timeline archive: {error}',
packaging_failed: 'Packaging failed: {error}',
update_acl_failed: 'Failed to update ACL: {error}',
upload_assets_errors: 'Upload assets completed with errors',
unauthorized: 'Unauthorized',
forbidden: 'Forbidden',
not_found: 'Not found',
......@@ -192,6 +191,7 @@ export default {
pending_invitations: 'Pending Invitations',
my_groups: 'My Groups',
my_vocabularies: 'My vocabularies',
my_assets: 'My Assets',
name: 'Name',
new_tag: 'New tag',
new_term: 'New term',
......@@ -216,11 +216,13 @@ export default {
size: 'Size',
tag: 'Tag',
tags: 'Tags',
target_path_optional: 'Target path (optional, e.g.: mycoolproject/videos)',
textual_body: 'Annotations',
title: 'Title',
title_unknown: 'Unknown Title',
timeline: 'Timeline',
type: 'Type',
upload_asset: 'Upload an asset file',
url: 'URL',
use_custom_date: 'Use custom date and time',
media_duration: 'Media duration',
......@@ -236,13 +238,10 @@ export default {
},
descriptions: {
access_control: 'Allow or disallow members of a group see your timeline and, optionally, the attached annotations.',
access_control_documents: 'Allow or disallow members of a group and/or the public to see your Document',
css_stylesheet: 'Either link an external stylesheet or set an inline value'
},
messages: {
acl_updated: 'Access settings updated',
document_created: 'Document created',
document_deleted: 'Document deleted',
login_success: 'Login successful',
logout_notice: 'You have been logged out',
registration_success: 'Account was successfully created. You can log in now!',
......@@ -258,7 +257,9 @@ export default {
confirm_delete: 'Delete this item?',
confirm_remove_member: 'Remove this member?',
confirm_delete_group: 'Delete this group?',
confirm_remove_asset: 'Delete this asset? Any existing references to it will be broken.',
updated_annotation: 'Updated annotation',
upload_assets_success: 'Assets uploaded successfully',
url_copied: 'URL copied to clipboard',
caution_media_time_override: 'Caution: Changing media reference time does not update associated annotations!',
browser_unsupported_warning: '<strong>Unsupported browser:</strong> For optimal performance please use ' +
......@@ -270,7 +271,9 @@ export default {
group_invite_request: 'You have been invited by {name} to join "{group}".',
invite_accepted: 'Invitation accepted',
invite_rejected: 'Invitation rejected',
confirm_remove_invitation: 'Remove invitation?'
confirm_remove_invitation: 'Remove invitation?',
delete_asset_success: 'Asset deleted successfully',
no_assets_uploaded: 'No assets have been uploaded.'
},
help: {
acl: {
......@@ -314,15 +317,9 @@ export default {
mosys_grids_edit: 'Edit',
mosys_grids_create: 'Create Grid'
},
documents: {
label: 'Documents',
documents_list: 'All Documents',
documents_copy_url: 'Copy URL',
documents_delete: 'Delete',
documents_download: 'Download',
documents_edit: 'Edit'
},
users: {
assets: 'Assets',
assets_upload: 'Upload asset files',
label: 'Account settings',
users_manage: 'Account settings',
groups: 'Groups'
......@@ -333,17 +330,6 @@ export default {
}
},
routes: {
documents: {
edit: {
title: 'Edit Document'
},
create: {
title: 'Add Document'
},
list: {
title: 'Documents'
}
},
annotate: {
media: {
title: 'Annotate Media',
......@@ -383,6 +369,13 @@ export default {
}
},
users: {
assets: {
title: 'Manage Assets',
caption: 'Add or delete asset files.'
},
assets_upload: {
title: 'Upload asset files'
},
create: {
title: 'Create Account',
caption: 'Fill out the form to create a new account.'
......
<template lang="pug">
full-screen
q-btn(v-if="!isMobile", slot="backButton", @click="$router.push({ name: 'documents.list' })",
icon="keyboard_backspace", round, small)
content-block(:position="'first'")
headline(:content="$t('routes.documents.create.title')")
content-paragraph(:position="'first'")
uploader(v-if="user", dark, :url="url", @finish="onFinish", :headers="headers", :fields="uploadFields")
</template>
<script>
import { mapGetters } from 'vuex'
import Headline from '../../components/shared/elements/Headline'
import ContentBlock from '../../components/shared/elements/ContentBlock'
import ContentParagraph from '../../components/shared/elements/ContentParagraph'
export default {
components: {
Headline,
ContentBlock,
ContentParagraph
},
data () {
return {
uploadFields: [],
headers: {
Authorization: `Bearer ${this.$auth.token}`
}
}
},
computed: {
...mapGetters({
user: 'auth/getUserState',
isMobile: 'globalSettings/getIsMobile'
}),
url () {
return this.user ? `${process.env.STORAGE_HOST}/files/user-${this.user.uuid}` : undefined
}
},
mounted () {
this.$root.$emit('setBackButton', 'list')
},
methods: {
onFinish (responses) {
for (let key of Object.keys(responses)) {
if (responses[key] && responses[key].message) {
this.$store.commit('notifications/addMessage', {
body: responses[key].message,
mode: 'alert',
type: 'error'
})
}
else {
this.$store.commit('notifications/addMessage', {
body: 'messages.document_created',
mode: 'alert',
type: 'success'
})
this.$router.push({ name: 'documents.list' })
}
}
}
}
}
</script>
<template lang="pug">
full-screen
//q-btn(v-if="!isMobile", slot="backButton", @click="$router.push({ name: 'documents.list' })", icon="keyboard_backspace", round, small)
content-block(v-if="availableRoles.length", :position="'last'")
headline(:content="$t('routes.documents.edit.title')")
| {{ $t('descriptions.access_control_documents') }}
content-paragraph(:position="'first'")
q-checkbox(v-model="acl.public", :label="$t('labels.public')", dark)
content-paragraph
q-select(v-model="acl.group", :clearable="true", :clear-value="undefined",
:float-label="$t('labels.access_control_add_group')", :options="availableRoles", dark)
content-paragraph
q-select(v-model="acl.group_remove", :clearable="true", :clear-value="undefined",
:float-label="$t('labels.access_control_remove_group')", :options="availableRoles", dark)
<!--q-field(dark)-->
<!--q-checkbox(v-model="acl.recursive", :label="$t('labels.recursive')", dark)-->
content-paragraph(:position="'last'")
.full-width.text-right.q-mt-sm
q-btn(:label="$t('buttons.update_access_control')", @click="updateACL", color="primary")
</template>
<script>
import { mapGetters } from 'vuex'
import { ObjectUtil } from 'mbjs-utils'
import Headline from '../../components/shared/elements/Headline'
import ContentBlock from '../../components/shared/elements/ContentBlock'
import ContentParagraph from '../../components/shared/elements/ContentParagraph'
export default {
components: {
Headline,
ContentBlock,
ContentParagraph
},
data () {
return {
acl: {
public: false,
group: undefined,
group_remove: undefined,
recursive: false
}
}
},
computed: {
...mapGetters({
user: 'auth/getUserState',
isMobile: 'globalSettings/getIsMobile'
}),
availableRoles () {
try {
const roles = this.user[`${process.env.AUTH0_APP_METADATA_PREFIX}roles`]
.filter(role => role !== 'user')
return roles.sort().map(role => { return { label: role, value: role } })
}
catch (e) {
return []
}
},
resourceName () {
return ObjectUtil.uuid5(`${this.$route.params.bucket}/${this.$route.params.asset}`)
}
},
async mounted () {
const aclQuery = {role: 'public', uuid: this.resourceName, permission: 'get'}
const permissions = await this.$store.dispatch('acl/isRoleAllowed', aclQuery)
this.acl.public = permissions.get === true
this.$root.$emit('setBackButton', 'list')
},
methods: {
async setACL (action, payload, recursive = false) {
await this.$store.dispatch(action, payload)
if (recursive) {
// TODO: add recursion to asset ACL
throw new Error('Recursion not implemented')
}
},
async updateACL () {
this.$q.loading.show()
if (this.acl.public) {
await this.setACL('acl/set', { role: 'public', id: this.resourceName, permissions: ['get'] }, this.acl.recursive)
}
else {
await this.setACL('acl/remove', { role: 'public', id: this.resourceName, permission: 'get' }, this.acl.recursive)
}
if (this.acl.group) {
await this.setACL('acl/set', { role: this.acl.group, id: this.resourceName, permissions: ['get'] }, this.acl.recursive)
}
if (this.acl.group_remove) {
await this.setACL('acl/remove', { role: this.acl.group_remove, id: this.resourceName, permission: 'get' }, this.acl.recursive)
}
this.$q.loading.hide()
this.$store.commit('notifications/addMessage', {
body: 'messages.acl_updated',
type: 'success'
})
}
}
}
</script>
<template lang="pug">
full-screen
confirm-modal(ref="confirmModal", @confirm="handleConfirmModal")
content-block(:position="'first'")
headline(:content="$t('routes.documents.list.title')")
content-paragraph(:position="'first'")
data-table(v-if="user", :config="config", :title="'routes.documents.list.title'", ref="listTable")
</template>
<script>
import { mapGetters } from 'vuex'
import { openURL } from 'quasar'
import Headline from '../../components/shared/elements/Headline'
import ContentBlock from '../../components/shared/elements/ContentBlock'
import ContentParagraph from '../../components/shared/elements/ContentParagraph'
export default {
components: {
Headline,
ContentBlock,
ContentParagraph
},
data () {
const _this = this
return {
assets: [],
config: {
columns: [
{
name: 'name',
label: this.$t('labels.name'),
field: 'name',
filter: true,
sortable: true
},
{
name: 'type',
label: this.$t('labels.type'),
field: row => row.metaData && row.metaData['content-type'] ? row.metaData['content-type'] : 'unknown'
},
{
name: 'size',
label: this.$t('labels.size'),
field: 'size',
format: val => `${(val / Math.pow(1024, 2)).toFixed(2)} MB`,
sortable: true
}
],
actions: [
{
type: 'url',
title: 'buttons.copy_url',
click: item => _this.copyToClipboard(_this.getAssetURL(item.name))
},
{
type: 'download',
title: 'buttons.download',
click: item => openURL(_this.getAssetURL(item.name, true))
},
{
type: 'edit',
title: 'buttons.edit',
click: item => _this.$router.push({ name: 'documents.edit', params: { asset: item.name, bucket: _this.bucketName } })
},
{
type: 'delete',
title: 'buttons.delete',
click: item => _this.$refs.confirmModal.show('buttons.delete', item, 'buttons.delete')
}
],
async request () {
return _this.$store.dispatch('files/list', _this.bucketName)
}
}
}
},
computed: {
...mapGetters({
user: 'auth/getUserState',
isMobile: 'globalSettings/getIsMobile'
}),
bucketName () {
return `user-${this.user.uuid}`
}
},
mounted () {
this.$root.$emit('setBackButton')
},
methods: {
getAssetURL (asset, download = false) {
const url = `${process.env.STORAGE_HOST}/files/user-${this.user.uuid}/${asset}`
if (download) return `${url}?dl=1`
return url
},
async copyToClipboard (text) {
try {
await navigator.clipboard.writeText(text)
this.$store.commit('notifications/addMessage', {
body: 'messages.url_copied',
type: 'success'
})
}
catch (err) {
this.$store.commit('notifications/addMessage', {
body: err.message,
type: 'error'
})
}
},
async handleConfirmModal (item) {
try {
await this.$store.dispatch