v5.0.0 - support new API

This commit is contained in:
jb-alvarado 2022-07-06 16:22:27 +02:00
parent b3a7839535
commit 9ed60c027f
21 changed files with 130 additions and 72 deletions

View File

@ -15,25 +15,22 @@ After installations you have to setup ssl for your **https** connections.
## Some Impressions: ## Some Impressions:
#### Login #### Login
![login](/docs/assets/login.png) ![login](/docs/images/login.png)
#### Landing Page
![landing-page](/docs/assets/landing-page.png)
#### Control Page #### Control Page
![control](/docs/assets/control.png) ![control](/docs/images/control.png)
#### Media Page #### Media Page
![media](/docs/assets/media.png) ![media](/docs/images/media.png)
#### Media Page / Upload #### Media Page / Upload
![media-upload](/docs/assets/media-upload.png) ![media-upload](/docs/images/media-upload.png)
#### Message Page #### Message Page
![message](/docs/assets/message.png) ![message](/docs/images/message.png)
#### Logging Page #### Logging Page
![logging](/docs/assets/logging.png) ![logging](/docs/images/logging.png)
#### Configuration Page / GUI #### Configuration Page / GUI
![config-gui](/docs/assets/config-gui.png) ![config-gui](/docs/images/config-gui.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

BIN
docs/images/config-gui.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
docs/images/control.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

BIN
docs/images/logging.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

BIN
docs/images/login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

BIN
docs/images/media.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

BIN
docs/images/message.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -1,7 +1,7 @@
{ {
"name": "ffplayout", "name": "ffplayout-frontend",
"version": "5.0.0", "version": "5.0.0",
"description": "web GUI for ffplayout engine", "description": "Web GUI for ffplayout",
"author": "Jonathan Baecker", "author": "Jonathan Baecker",
"private": true, "private": true,
"scripts": { "scripts": {

View File

@ -21,7 +21,7 @@
v-for="(crumb, index) in crumbs" v-for="(crumb, index) in crumbs"
:key="crumb.key" :key="crumb.key"
:active="index === crumbs.length - 1" :active="index === crumbs.length - 1"
@click="getPath(extensions, crumb.path)" @click="getPath(crumb.path)"
> >
{{ crumb.text }} {{ crumb.text }}
</b-breadcrumb-item> </b-breadcrumb-item>
@ -43,7 +43,7 @@
<b-icon-folder-fill class="browser-icons" /> <b-icon-folder-fill class="browser-icons" />
</b-col> </b-col>
<b-col class="browser-item-text"> <b-col class="browser-item-text">
<b-link @click="getPath(extensions, `/${folderTree.source}/${folder}`)"> <b-link @click="getPath(`/${folderTree.source}/${folder}`)">
{{ folder }} {{ folder }}
</b-link> </b-link>
</b-col> </b-col>
@ -304,7 +304,7 @@ export default {
watch: { watch: {
configID () { configID () {
this.getPath(this.extensions, '') this.getPath('')
} }
}, },
@ -314,14 +314,14 @@ export default {
}) })
this.extensions = exts.join(',') this.extensions = exts.join(',')
this.getPath(this.extensions, '') this.getPath('')
}, },
methods: { methods: {
async getPath (extensions, path) { async getPath (path) {
this.lastPath = path this.lastPath = path
this.isLoading = true this.isLoading = true
await this.$store.dispatch('media/getTree', { extensions, path }) await this.$store.dispatch('media/getTree', { path })
this.isLoading = false this.isLoading = false
}, },
@ -369,7 +369,7 @@ export default {
) )
this.$root.$emit('bv::hide::modal', 'folder-modal') this.$root.$emit('bv::hide::modal', 'folder-modal')
this.getPath(this.extensions, this.lastPath) this.getPath(this.lastPath)
}, },
onCancelCreateFolder (evt) { onCancelCreateFolder (evt) {
@ -429,7 +429,7 @@ export default {
this.currentNumber = 0 this.currentNumber = 0
this.inputPlaceholder = 'Choose files or drop them here...' this.inputPlaceholder = 'Choose files or drop them here...'
this.inputFiles = [] this.inputFiles = []
this.getPath(this.extensions, this.lastPath) this.getPath(this.lastPath)
this.$root.$emit('bv::hide::modal', 'upload-modal') this.$root.$emit('bv::hide::modal', 'upload-modal')
}, },
@ -442,7 +442,7 @@ export default {
this.inputPlaceholder = 'Choose files or drop them here...' this.inputPlaceholder = 'Choose files or drop them here...'
this.cancelTokenSource.cancel('Upload cancelled') this.cancelTokenSource.cancel('Upload cancelled')
this.getPath(this.extensions, this.lastPath) this.getPath(this.lastPath)
this.$root.$emit('bv::hide::modal', 'upload-modal') this.$root.$emit('bv::hide::modal', 'upload-modal')
}, },
@ -489,7 +489,7 @@ export default {
} }
) )
this.getPath(this.extensions, this.lastPath) this.getPath(this.lastPath)
this.renamePath = '' this.renamePath = ''
this.renameOldName = '' this.renameOldName = ''
@ -537,7 +537,7 @@ export default {
this.$root.$emit('bv::hide::modal', 'delete-modal') this.$root.$emit('bv::hide::modal', 'delete-modal')
this.getPath(this.extensions, this.lastPath) this.getPath(this.lastPath)
}, },
cancelDelete () { cancelDelete () {

View File

@ -266,13 +266,13 @@ export default {
}, },
watch: { watch: {
selected (id) { selected (index) {
this.getPreset(id) this.getPreset(index)
} }
}, },
created () { created () {
this.getPreset('') this.getPreset(null)
}, },
methods: { methods: {
@ -284,34 +284,34 @@ export default {
return (parseFloat(parseInt(num, 16)) / 255).toFixed(2) return (parseFloat(parseInt(num, 16)) / 255).toFixed(2)
}, },
async getPreset (id) { async getPreset (index) {
const response = await this.$axios.get(`api/presets/${this.configGui[this.configID].id}`) const response = await this.$axios.get(`api/presets/${this.configGui[this.configID].id}`)
if (response.data && !id) { if (response.data && index === null) {
this.presets = [] this.presets = []
for (const item of response.data) { for (let index = 0; index < response.data.length; index++) {
this.presets.push({ value: item.id, text: item.name }) const elem = response.data[index]
this.presets.push({ value: index, text: elem.name })
} }
} else if (response.data) { } else if (response.data) {
id -= 1 const fColor = response.data[index].fontcolor.split('@')
const fColor = response.data[id].fontcolor.split('@') const bColor = response.data[index].boxcolor.split('@')
const bColor = response.data[id].boxcolor.split('@')
this.form = { this.form = {
id: response.data[id].id, id: response.data[index].id,
name: response.data[id].name, name: response.data[index].name,
text: response.data[id].text, text: response.data[index].text,
x: response.data[id].x, x: response.data[index].x,
y: response.data[id].y, y: response.data[index].y,
fontSize: response.data[id].fontsize, fontSize: response.data[index].fontsize,
fontSpacing: response.data[id].line_spacing, fontSpacing: response.data[index].line_spacing,
fontColor: fColor[0], fontColor: fColor[0],
fontAlpha: (fColor[1]) ? this.hexToDec(fColor[1]) : 1.0, fontAlpha: (fColor[1]) ? this.hexToDec(fColor[1]) : 1.0,
showBox: response.data[id].box, showBox: response.data[index].box,
boxColor: bColor[0], boxColor: bColor[0],
boxAlpha: (bColor[1]) ? this.hexToDec(bColor[1]) : 1.0, boxAlpha: (bColor[1]) ? this.hexToDec(bColor[1]) : 1.0,
border: response.data[id].boxborderw, border: response.data[index].boxborderw,
overallAlpha: response.data[id].alpha overallAlpha: response.data[index].alpha
} }
} }
}, },
@ -344,7 +344,7 @@ export default {
if (response.status === 200) { if (response.status === 200) {
this.success = true this.success = true
this.getPreset('') this.getPreset(null)
} else { } else {
this.failed = true this.failed = true
} }
@ -394,7 +394,7 @@ export default {
} }
this.$bvModal.hide('delete-modal') this.$bvModal.hide('delete-modal')
this.getPreset('') this.getPreset(null)
}, },
async submitMessage () { async submitMessage () {

View File

@ -152,7 +152,7 @@
v-for="(crumb, index) in crumbs" v-for="(crumb, index) in crumbs"
:key="crumb.key" :key="crumb.key"
:active="index === crumbs.length - 1" :active="index === crumbs.length - 1"
@click="getPath(extensions, crumb.path)" @click="getPath(crumb.path)"
> >
{{ crumb.text }} {{ crumb.text }}
</b-breadcrumb-item> </b-breadcrumb-item>
@ -166,7 +166,7 @@
:key="folder.key" :key="folder.key"
class="browser-item" class="browser-item"
> >
<b-link @click="getPath(extensions, `/${folderTree.source}/${folder}`)"> <b-link @click="getPath(`/${folderTree.source}/${folder}`)">
<b-icon-folder-fill class="browser-icons" /> {{ folder }} <b-icon-folder-fill class="browser-icons" /> {{ folder }}
</b-link> </b-link>
</b-list-group-item> </b-list-group-item>
@ -310,6 +310,9 @@
<b-button v-if="!configPlayout.playlist.loop" v-b-tooltip.hover title="Loop Clips in Playlist" variant="primary" @click="loopClips()"> <b-button v-if="!configPlayout.playlist.loop" v-b-tooltip.hover title="Loop Clips in Playlist" variant="primary" @click="loopClips()">
<b-icon-view-stacked /> <b-icon-view-stacked />
</b-button> </b-button>
<b-button v-b-tooltip.hover title="Add (remote) Source to Playlist" variant="primary" @click="showAddSource()">
<b-icon-file-earmark-plus />
</b-button>
<b-button v-b-tooltip.hover title="Generate a randomized Playlist" variant="primary" @click="generatePlaylist(listDate)"> <b-button v-b-tooltip.hover title="Generate a randomized Playlist" variant="primary" @click="generatePlaylist(listDate)">
<b-icon-sort-down-alt /> <b-icon-sort-down-alt />
</b-button> </b-button>
@ -351,6 +354,35 @@
> >
Delete program from {{ listDate }} Delete program from {{ listDate }}
</b-modal> </b-modal>
<b-modal
id="add-source-modal"
ref="add-source-modal"
title="Add (remote) Source"
@ok="handleSource"
>
<form ref="form" @submit.stop.prevent="addSource">
<b-form-group label="In" label-for="in-input">
<b-form-input id="in-input" v-model.number="newSource.in" type="number" inline />
</b-form-group>
<b-form-group label="Out" label-for="out-input" invalid-feedback="Out is required">
<b-form-input id="out-input" v-model.number="newSource.out" type="number" inline required />
</b-form-group>
<b-form-group label="Duration" label-for="duration-input" invalid-feedback="Out is required">
<b-form-input id="duration-input" v-model.number="newSource.duration" type="number" inline required />
</b-form-group>
<b-form-group label="Source" label-for="source-input" invalid-feedback="Source is required">
<b-form-input id="source-input" v-model="newSource.source" required />
</b-form-group>
<b-form-checkbox
id="ad-input"
v-model="newSource.category"
value="advertisement"
:unchecked-value="newSource.category"
>
Advertisement
</b-form-checkbox>
</form>
</b-modal>
</div> </div>
</template> </template>
@ -398,7 +430,6 @@ export default {
listDate: this.$dayjs().tz(this.timezone).format('YYYY-MM-DD'), listDate: this.$dayjs().tz(this.timezone).format('YYYY-MM-DD'),
targetDate: this.$dayjs().tz(this.timezone).format('YYYY-MM-DD'), targetDate: this.$dayjs().tz(this.timezone).format('YYYY-MM-DD'),
interval: null, interval: null,
extensions: '',
videoOptions: { videoOptions: {
liveui: true, liveui: true,
controls: true, controls: true,
@ -424,6 +455,14 @@ export default {
scrollOP: { scrollOP: {
suppressScrollX: true, suppressScrollX: true,
minScrollbarLength: 30 minScrollbarLength: 30
},
newSource: {
begin: 0,
in: 0,
out: 0,
duration: 0,
category: '',
source: ''
} }
} }
}, },
@ -460,7 +499,7 @@ export default {
} }
] ]
this.getPath(this.extensions, '') this.getPath('')
this.getPlaylist() this.getPlaylist()
setTimeout(() => { scrollTo(this) }, 3000) setTimeout(() => { scrollTo(this) }, 3000)
} }
@ -475,8 +514,7 @@ export default {
] ]
this.getStatus() this.getStatus()
this.extensions = this.configPlayout.storage.extensions.join(',') await this.getPath('')
await this.getPath(this.extensions, '')
const timeInSec = this.$timeToSeconds(this.$dayjs().tz(this.timezone).format('HH:mm:ss')) const timeInSec = this.$timeToSeconds(this.$dayjs().tz(this.timezone).format('HH:mm:ss'))
const listStartSec = this.$timeToSeconds(this.configPlayout.playlist.day_start) const listStartSec = this.$timeToSeconds(this.configPlayout.playlist.day_start)
@ -489,20 +527,15 @@ export default {
}, },
mounted () { mounted () {
// if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
// this.interval = setInterval(() => { this.interval = setInterval(() => {
// this.$store.dispatch('playlist/animClock') this.$store.dispatch('playlist/playoutStat')
// this.getStatus() this.getStatus()
// }, 5000) }, 5000)
// } else {
// this.$store.dispatch('playlist/animClock')
// }
this.interval = setInterval(() => {
this.$store.dispatch('playlist/playoutStat') this.$store.dispatch('playlist/playoutStat')
this.getStatus() } else {
}, 5000) this.$store.dispatch('playlist/playoutStat')
this.$store.dispatch('playlist/playoutStat') }
const streamExtension = this.configGui[this.configID].preview_url.split('.').pop() const streamExtension = this.configGui[this.configID].preview_url.split('.').pop()
let player let player
@ -533,9 +566,9 @@ export default {
}, },
methods: { methods: {
async getPath (extensions, path) { async getPath (path) {
this.browserIsLoading = true this.browserIsLoading = true
await this.$store.dispatch('media/getTree', { extensions, path }) await this.$store.dispatch('media/getTree', { path })
this.browserIsLoading = false this.browserIsLoading = false
}, },
@ -591,14 +624,12 @@ export default {
this.$root.$emit('bv::show::modal', 'preview-modal') this.$root.$emit('bv::show::modal', 'preview-modal')
}, },
cloneClip ({ file, duration }) { cloneClip ({ name, duration }) {
const storagePath = this.configPlayout.storage.path const storagePath = this.configPlayout.storage.path
const storagePathArr = storagePath.split('/') const sourcePath = `${storagePath}/${this.folderTree.source}/${name}`.replace('//', '/')
const storageRoot = storagePathArr.pop()
const sourcePath = `${storagePathArr.join('/')}/${this.folderTree.tree[0].substring(this.folderTree.tree[0].indexOf(storageRoot))}`
return { return {
source: `${sourcePath}/${file}`, source: sourcePath,
in: 0, in: 0,
out: duration, out: duration,
duration duration
@ -709,6 +740,36 @@ export default {
showDeleteModal () { showDeleteModal () {
this.$root.$emit('bv::show::modal', 'delete-modal') this.$root.$emit('bv::show::modal', 'delete-modal')
},
showAddSource () {
this.$bvModal.show('add-source-modal')
},
handleSource (bvModalEvt) {
// Prevent modal from closing
bvModalEvt.preventDefault()
// Trigger submit handler
this.addSource()
},
addSource () {
const list = this.playlist
list.push(this.newSource)
this.$store.commit('playlist/UPDATE_PLAYLIST', this.$processPlaylist(this.startInSec, list))
this.newSource = {
begin: 0,
in: 0,
out: 0,
duration: 0,
category: '',
source: ''
}
this.$nextTick(() => {
this.$bvModal.hide('add-source-modal')
})
} }
} }
} }

View File

@ -17,7 +17,7 @@ export const mutations = {
} }
export const actions = { export const actions = {
async getTree ({ commit, dispatch, state, rootState }, { extensions, path }) { async getTree ({ commit, dispatch, state, rootState }, { path }) {
const crumbs = [] const crumbs = []
let root = '/' let root = '/'
const channel = rootState.config.configGui[rootState.config.configID].id const channel = rootState.config.configGui[rootState.config.configID].id