work on item automation

This commit is contained in:
Kode 2020-02-25 11:27:29 +00:00
parent 14a160dcd8
commit 4f00a2b834
9 changed files with 252 additions and 263 deletions

View File

@ -32,7 +32,7 @@ class Item extends Model {
},
config: {
type: DataTypes.TEXT
},
}/* ,
tags: {
type: DataTypes.TEXT,
set (val) {
@ -41,7 +41,7 @@ class Item extends Model {
get () {
return JSON.parse(this.tags)
}
}
} */
}, {
sequelize,
underscored: true,

View File

@ -39,7 +39,7 @@ export default {
mounted () {
console.log(this.user)
this.$store.dispatch('app/status')
// this.$store.dispatch('tiles/getApps')
this.$store.dispatch('tiles/getApps')
// this.$store.dispatch('tags/getTags')
// this.$store.dispatch('tiles/getPossibleApps')
this.$store.dispatch('users/getUsers')

View File

@ -1,202 +1,91 @@
<template>
<q-card style="width: calc(100% - 60px); max-width: 900px; background: #fcfcfc;">
<div class="popup-header">
<div class="text-h6">Application</div>
<div class="actions">
<q-btn @click="save" flat>
<q-icon name="save" />
Save
</q-btn>
<q-btn flat v-close-popup>
<q-icon name="block" />
Cancel
</q-btn>
<div class="list-item" :class="{ edit: this.showback === true}">
<div class="list-item-inner">
<div class="list-item-front">
<div class="icon"><img class="app-icon" :src="'http://heimdallmaster.test/storage/' + this.icon"></div>
<div class="name">
{{ this.application.title }}
<span class="tags">Tags: {{ this.taglist }}</span>
<a :href="this.application.url">url</a>
</div>
<div class="pinned">
Active
<q-icon
:name="this.application.pinned === '1' ? 'check_box' : 'check_box_outline_blank'"
/>
</div>
<div class="actions">
<q-btn size="12px" unelevated color="primary" @click="edit = true">Edit</q-btn>
<q-btn size="12px" unelevated color="grey-2" @click="showBack()" text-color="black">Delete</q-btn>
</div>
</div>
<div class="list-item-back">
<div class="confirmation">
Are you sure you want to delete this item?
<div>
<q-btn style="width: 120px;" unelevated color="red-10" @click="edit = true">Yes</q-btn>
<q-btn size="12px" unelevated @click="hideBack()">Cancel</q-btn>
</div>
</div>
<q-separator />
<q-card-section class="q-pt-none">
</div>
</div>
<div id="create" class="create">
<input type="hidden" name="_token" value="St25kITgoR7VYADJsYA0KBAAfTPcVYBQ5JqUR3dt">
<div class="input">
<q-input
outlined
v-model="title"
:label="this.$t('application_name')"
class="my-input"
>
</q-input>
</div>
<div class="input">
<q-select
outlined
:options="possibleapps"
option-value="appid"
option-label="name"
map-options
:label="this.$t('application_type')"
v-model="applicationtype"
></q-select>
</div>
<div class="input" style="position: relative;">
<q-input
outlined
v-model="color"
:label="this.$t('colour')"
class="my-input"
>
<template v-slot:append>
<q-icon name="colorize" class="cursor-pointer">
<q-popup-proxy transition-show="scale" transition-hide="scale">
<q-color v-model="color" format-model="hexa" />
</q-popup-proxy>
</q-icon>
</template>
</q-input>
</div>
<div class="input">
<q-input
outlined
v-model="url"
class="my-input"
:label="this.$t('url')"
>
</q-input>
</div>
<div class="input double">
<q-select
:label="this.$t('Tags')"
outlined
v-model="tags"
multiple
:options="possibletags"
use-input
new-value-mode="add-unique"
emit-value
use-chips
@filter="filterFn"
/>
</div>
<div class="input">
<div class="icon-container">
<div id="appimage">
<img style="width: 140px" :src="icon" />
</div>
<div class="upload-btn-wrapper">
<button class="btn">Upload a file </button>
<input type="file" id="upload" name="file">
</div>
</div>
</div>
<div class="input">
<label>Preview</label>
<tile
:application="preview"
></tile>
</div>
</div>
</q-card-section>
<div id="sapconfig"></div>
<q-separator />
<div class="popup-header">
<div class="text-h6"></div>
<div class="actions">
<q-btn @click="save" flat>
<q-icon name="save" />
Save
</q-btn>
<q-btn flat v-close-popup>
<q-icon name="block" />
Cancel
</q-btn>
</div>
</div>
</q-card>
<q-dialog v-model="edit">
<edit-tile :application="application"></edit-tile>
</q-dialog>
</div>
</template>
<script>
import Tile from './Tile'
import EditTile from 'components/EditTile'
export default {
name: 'EditTile',
name: 'AppItem',
props: ['application'],
components: {
Tile
EditTile
},
computed: {
possibleapps () {
return this.$store.state.tiles.possibleapps
},
possibletags () {
return this.$store.state.tags.all
},
taglist () {
return this.tags.map(a => a.title).join()
return null
// return this.tags.map(a => a.title).join()
},
preview () {
return {
color: this.color || '#222222',
color: this.color,
title: this.title,
icon: this.icon,
url: this.url
}
},
tagsParse () {
if (this.application.tags === null) {
return []
}
return JSON.parse(this.application.tags)
}
},
data () {
return {
color: this.application.color,
applicationtype: this.application.applicationType,
edit: false,
showback: false,
color: this.application.colour,
applicationtype: this.application.appid,
title: this.application.title,
tags: this.tagsParse,
tags: this.application.tags,
url: this.application.url,
icon: this.application.icon || '../img/heimdall-icon-small.png',
possibletags: this.tagsParse
icon: this.application.icon || '../img/heimdall-icon-small.png'
}
},
methods: {
save: function () {
// check if new or edit
this.$store.dispatch('tiles/save', {
color: this.color,
applicationType: this.applicationtype.appid,
title: this.title,
tags: JSON.stringify(this.tags),
url: this.url,
icon: this.icon
})
showBack: function () {
this.showback = true
},
filterFn (val, update) {
update(() => {
if (val === '') {
this.possibletags = this.application.tags || []
} else {
const needle = val.toLowerCase()
const tags = this.application.tags || []
this.possibletags = tags.filter(
v => v.toLowerCase().indexOf(needle) > -1
)
}
})
hideBack: function () {
this.showback = false
}
}
}
</script>
<style lang="scss">
</style>

View File

@ -4,13 +4,14 @@
<q-scroll-area style="height: 100%;">
<div id="create" class="create fit">
<div class="user-details">
<div @click="changeavatar = !changeavatar" class="changephoto">
<q-icon size="30px" name="photo_camera" />
Change Image
<div class="buttons">
<q-btn unelevated @click="applicationdialog = true" color="grey-5">Application</q-btn>
<q-btn unelevated @click="websitedialog = true" color="grey-5">Website</q-btn>
<q-btn unelevated color="grey-5">Docker</q-btn>
</div>
<q-avatar size="220px" style="background: #c1c1c1;" class="user-avatar">
<div class="avatar-resize"><img :src="this.seticon"></div>
</q-avatar>
<tile
:application="preview"
></tile>
<div v-if="changeicon">
<q-file outlined v-model="avatar" :label="this.$t('upload_file')">
@ -45,7 +46,13 @@
clickable
v-ripple
name="general"
label="Profile"
label="General"
/>
<q-tab
clickable
v-ripple
name="image"
label="Look"
/>
<q-tab
clickable
@ -53,18 +60,6 @@
name="mfa"
label="Security"
/>
<q-tab
clickable
v-ripple
name="search"
label="Homepage search"
/>
<q-tab
clickable
v-ripple
name="background"
label="Background Image"
/>
</q-tabs>
<q-tab-panels v-model="tab" animated class="">
@ -73,19 +68,43 @@
<q-input
outlined
v-model="title"
:label="this.$t('application_name')"
:label="this.$t('title')"
class="my-input"
>
</q-input>
<q-input
outlined
v-model="url"
class="my-input"
:label="this.$t('url')"
>
</q-input>
<q-input
v-model="description"
outlined
type="textarea"
/>
<q-select
outlined
:options="possibleapps"
option-value="appid"
option-label="name"
map-options
:label="this.$t('application_type')"
v-model="applicationtype"
></q-select>
:label="this.$t('Tags')"
outlined
v-model="tags"
multiple
:options="possibletags"
use-input
new-value-mode="add-unique"
emit-value
use-chips
@filter="filterFn"
/>
</q-tab-panel>
<q-tab-panel name="mfa">
</q-tab-panel>
<q-tab-panel name="image">
<q-input
outlined
v-model="color"
@ -100,55 +119,12 @@
</q-icon>
</template>
</q-input>
<q-input
outlined
v-model="url"
class="my-input"
:label="this.$t('url')"
>
</q-input>
<q-select
:label="this.$t('Tags')"
outlined
v-model="tags"
multiple
:options="possibletags"
use-input
new-value-mode="add-unique"
emit-value
use-chips
@filter="filterFn"
/>
<div class="icon-container">
<div id="appimage">
<img style="width: 140px" :src="icon" />
</div>
<div class="upload-btn-wrapper">
<q-file outlined ref="icon" v-model="icon" label="Icon" />
</div>
</div>
<div class="input">
<label>Preview</label>
<tile
:application="preview"
></tile>
</div>
</q-tab-panel>
<q-tab-panel name="mfa">
</q-tab-panel>
<q-tab-panel name="search">
<div class="text-h6">Search</div>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
</q-tab-panel>
<q-tab-panel name="background">
<div class="text-h6">Background</div>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
</q-tab-panel>
</q-tab-panels>
@ -166,6 +142,64 @@
</q-btn>
</div>
</q-form>
<q-dialog v-model="applicationdialog">
<q-card>
<q-card-section>
<div class="text-h6">Select Application</div>
</q-card-section>
<q-card-section style="width: 500px;" class="q-pt-none">
<q-select
outlined
:options="possibleapps"
option-value="appid"
option-label="name"
map-options
:label="this.$t('application_type')"
v-model="applicationtype"
></q-select>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Cancel" color="primary" v-close-popup />
<q-btn unelevated label="Set" @click="setApplication" color="primary" v-close-popup />
</q-card-actions>
</q-card>
</q-dialog>
<q-dialog v-model="websitedialog">
<q-card>
<q-card-section>
<div class="text-h6">Enter website address</div>
</q-card-section>
<q-card-section style="width: 500px;" class="q-pt-none">
<q-input
outlined
v-model="website"
:label="this.$t('website')"
></q-input>
<q-btn flat label="Lookup" @click="getWebsiteData" color="primary" />
<div class="iconlist" v-if="websitedata">
<div
class="icon"
:class="{ selected: key === selectedwebsiteimage }"
v-for="(icon, key) in websitedata.icons"
:key="key"
@click="selectWebsiteImage(key)"
>
<img :src="icon" />
</div>
</div>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Cancel" color="primary" v-close-popup />
<q-btn unelevated v-if="websitedata !== null" label="Set" @click="setWebsite" color="primary" v-close-popup />
</q-card-actions>
</q-card>
</q-dialog>
</div>
</template>
@ -200,8 +234,9 @@ export default {
return {
color: this.color || '#222222',
title: this.title,
icon: this.icon,
url: this.url
icon: this.seticon,
url: this.url,
preview: true
}
},
tagsParse () {
@ -211,6 +246,7 @@ export default {
return JSON.parse(this.application.tags)
},
seticon () {
if (this.icon) return this.icon
return (this.application.icon !== null) ? process.env.BACKEND_LOCATION + this.application.icon : 'https://apps.heimdall.site/img/heimdall-logo-white.svg'
}
},
@ -224,12 +260,19 @@ export default {
tags: null,
url: null,
icon: null,
description: '',
actions: false,
possibletags: this.tagsParse,
submitEmpty: false,
submitResult: [],
changeicon: false,
tab: 'general'
tab: 'general',
applicationdialog: false,
websitedialog: false,
dockerdialog: false,
websitedata: null,
selectedwebsiteimage: null,
website: null
}
},
@ -279,6 +322,20 @@ export default {
}
})
},
setApplication () {
this.title = this.applicationtype.name
this.description = this.applicationtype.description
this.icon = 'https://raw.githubusercontent.com/linuxserver/Heimdall-Apps/master/' + this.applicationtype.name + '/' + this.applicationtype.icon
},
setWebsite () {
this.title = this.websitedata.title
this.description = this.websitedata.description
this.icon = this.websitedata.icons[this.selectedwebsiteimage]
this.url = this.website
},
selectWebsiteImage (key) {
this.selectedwebsiteimage = key
},
closeCreate () {
this.actions = false
setTimeout(async function () {
@ -287,16 +344,25 @@ export default {
}.bind(this), 300)
},
async getIcons () {
async getWebsiteData () {
try {
const html = await fetch('https://cors-anywhere.herokuapp.com/' + this.url)
const websitedata = {}
const html = await fetch('https://cors-anywhere.herokuapp.com/' + this.website)
// console.log(html)
const parser = new DOMParser()
const document = parser.parseFromString(await html.text(), 'text/html')
const links = document.getElementsByTagName('link')
websitedata.title = document.getElementsByTagName('title')[0].innerText
const metas = document.getElementsByTagName('meta')
const icons = []
for (let i = 0; i < metas.length; i++) {
if (metas[i].getAttribute('name') === 'description') {
websitedata.description = metas[i].getAttribute('content')
}
}
for (let i = 0; i < links.length; i++) {
const link = links[i]
@ -324,14 +390,14 @@ export default {
// This is of course assuming the script is executing in the browser
// Node.js is a different story! As I would be using cheerio.js for parsing the html instead of document.
// Also you would use the response.headers object for Node.js below.
console.log('link: ' + this.url)
console.log('link: ' + this.website)
let finalBase = ''
if (this.url.endsWith('/')) {
const baseurl = this.url.split('/')
if (this.website.endsWith('/')) {
const baseurl = this.website.split('/')
baseurl.pop()
finalBase = baseurl.join('/')
} else {
finalBase = this.url
finalBase = this.website
}
let absoluteHref = finalBase
@ -364,8 +430,9 @@ export default {
}
}
}
console.log(icons)
websitedata.icons = icons
this.websitedata = websitedata
console.log(websitedata)
} catch (e) {
console.log(e)
}
@ -374,3 +441,20 @@ export default {
}
</script>
<style lang="scss">
.iconlist {
display: flex;
flex-wrap: wrap;
.icon {
display: flex;
justify-content: center;
align-items: center;
background: #f1f1f1;
border: 4px solid #f1f1f1;
margin: 8px;
&.selected {
border: 4px solid #724c7a;
}
}
}
</style>

View File

@ -4,12 +4,12 @@
<q-scroll-area style="height: 100%;">
<div id="create" class="create fit">
<div class="user-details">
<div @click="changeavatar = !changeavatar" class="changephoto">
<q-icon size="30px" name="photo_camera" />
Change Image
</div>
<q-avatar size="220px" style="background: #c1c1c1;" class="user-avatar">
<div class="avatar-resize"><img :src="this.setavatar"></div>
<div @click="changeavatar = !changeavatar" class="changephoto">
<q-icon size="30px" name="photo_camera" />
Change Image
</div>
</q-avatar>
<div v-if="changeavatar">

View File

@ -1,11 +1,11 @@
<template>
<section class="item-container ui-sortable-handle" :data-id="this.$attrs.id">
<section class="item-container ui-sortable-handle" :class="{ preview: preview }" :data-id="this.$attrs.id">
<div class="item" :style="'background-color: ' + this.application.color + '; color: ' + textColor + ''">
<img class="app-icon" :src="this.icon">
<img class="app-icon" :src="this.application.icon">
<div class="details">
<div class="title white">{{ application.title }}</div>
</div>
<a :style="'color: ' + textColor" title="NZBGet is a binary downloader, which downloads files from Usenet based on information given in nzb-files. NZBGet is written in C++ and is known for its extraordinary performance and efficiency. - GNU General Public License v2.0 only" class="link white" href="http://test.com"><svg class="svg-inline--fa fa-arrow-alt-to-right fa-w-14" aria-hidden="true" data-prefix="fas" data-icon="arrow-alt-to-right" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" data-fa-i2svg=""><path fill="currentColor" d="M448 88v336c0 13.3-10.7 24-24 24h-24c-13.3 0-24-10.7-24-24V88c0-13.3 10.7-24 24-24h24c13.3 0 24 10.7 24 24zM24 320h136v87.7c0 17.8 21.5 26.7 34.1 14.1l152.2-152.2c7.5-7.5 7.5-19.8 0-27.3L194.1 90.1c-12.6-12.6-34.1-3.7-34.1 14.1V192H24c-13.3 0-24 10.7-24 24v80c0 13.3 10.7 24 24 24z"></path></svg><!-- <i class="fas fa-arrow-alt-to-right"></i> --></a>
<a :style="'color: ' + textColor" :title="application.description" class="link white" :href="application.url"><svg class="svg-inline--fa fa-arrow-alt-to-right fa-w-14" aria-hidden="true" data-prefix="fas" data-icon="arrow-alt-to-right" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" data-fa-i2svg=""><path fill="currentColor" d="M448 88v336c0 13.3-10.7 24-24 24h-24c-13.3 0-24-10.7-24-24V88c0-13.3 10.7-24 24-24h24c13.3 0 24 10.7 24 24zM24 320h136v87.7c0 17.8 21.5 26.7 34.1 14.1l152.2-152.2c7.5-7.5 7.5-19.8 0-27.3L194.1 90.1c-12.6-12.6-34.1-3.7-34.1 14.1V192H24c-13.3 0-24 10.7-24 24v80c0 13.3 10.7 24 24 24z"></path></svg><!-- <i class="fas fa-arrow-alt-to-right"></i> --></a>
</div>
</section>
</template>
@ -33,6 +33,9 @@ export default {
const a = parseFloat(parseInt((parseInt(alpha, 16) / 255) * 1000) / 1000)
const brightness = r * 0.299 + g * 0.587 + b * 0.114 + (1 - a) * 255
return brightness > 186 ? darkColor : lightColor
},
preview () {
return this.application.preview
}
},

View File

@ -115,7 +115,11 @@ h1 {
margin-right: 15px;
}
}
&.preview {
.item {
margin: 20px 0;
}
}
}
.svg-inline--fa {
display: inline-block;
@ -596,9 +600,9 @@ div.create {
align-items: center;
background: #00000059;
color: #ffffffd1;
top: 90px;
top: 50%;
left: 50%;
transform: translateX(-50%);
transform: translate(-50%, -50%);
z-index: 5;
width: 120px;
height: 120px;
@ -607,6 +611,7 @@ div.create {
font-weight: bold;
font-size: 12px;
text-transform: uppercase;
line-height: 1.4;
}
.useractions {
position: absolute;
@ -615,12 +620,12 @@ div.create {
bottom: 0;
width: 70px;
background: #724c7a;
transform: translateX(80px);
transform: translateX(0);
transition: all 0.3s;
color: white;
padding: 10px 0;
&.active {
transform: translateX(0);
&.inactive {
transform: translateX(80px);
}
button {
padding: 10px 0;
@ -640,6 +645,13 @@ div.create {
max-width: 340px;
text-align: center;
position: relative;
.buttons {
display: flex;
flex-direction: column;
button {
margin-bottom: 10px;
}
}
.q-list {
}
.q-avatar {

View File

@ -35,7 +35,7 @@ export default {
mfa_no_qr: 'If you have problems scanning the QR code, do a manual setup with code: {code}.',
code: 'Code',
// Edit / Create tiles
application_name: 'Application name',
name: 'Name',
application_type: 'Application type',
colour: 'Color',
upload_file: 'Upload File',

View File

@ -57,6 +57,7 @@ export default {
return this.$store.state.tiles.create
},
set (val) {
this.$store.dispatch('tiles/getPossibleApps')
this.$store.commit('tiles/create', val)
}
}