forked from platypush/platypush
Migrated settings panel and logout button
This commit is contained in:
parent
201bb5986f
commit
748609c6f4
41 changed files with 869 additions and 92 deletions
2
platypush/backend/http/dist/index.html
vendored
2
platypush/backend/http/dist/index.html
vendored
|
@ -1 +1 @@
|
|||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>platypush</title><link href="/static/css/chunk-134ec1dc.849ccfd5.css" rel="prefetch"><link href="/static/css/chunk-13b07ca5.029dd736.css" rel="prefetch"><link href="/static/css/chunk-1653b664.5b949e24.css" rel="prefetch"><link href="/static/css/chunk-23726328.7e460329.css" rel="prefetch"><link href="/static/css/chunk-283aacba.f186cc51.css" rel="prefetch"><link href="/static/css/chunk-2ca39dde.efa1eae8.css" rel="prefetch"><link href="/static/css/chunk-2f304dee.a8a2d99a.css" rel="prefetch"><link href="/static/css/chunk-3b435dde.f186cc51.css" rel="prefetch"><link href="/static/css/chunk-487896e7.b7730bd4.css" rel="prefetch"><link href="/static/css/chunk-49211740.43a25f0f.css" rel="prefetch"><link href="/static/css/chunk-4dae396b.92b3713e.css" rel="prefetch"><link href="/static/css/chunk-5145872a.197de139.css" rel="prefetch"><link href="/static/css/chunk-53e279b3.f186cc51.css" rel="prefetch"><link href="/static/css/chunk-595ffc05.678c9c97.css" rel="prefetch"><link href="/static/css/chunk-5a1e13e4.f186cc51.css" rel="prefetch"><link href="/static/css/chunk-5d5c4530.75269c9b.css" rel="prefetch"><link href="/static/css/chunk-64076603.e451beea.css" rel="prefetch"><link href="/static/css/chunk-675c7703.75b51be7.css" rel="prefetch"><link href="/static/css/chunk-792fd41e.4d467174.css" rel="prefetch"><link href="/static/css/chunk-7fae0422.c233115f.css" rel="prefetch"><link href="/static/css/chunk-d22da0c0.7c71cffb.css" rel="prefetch"><link href="/static/css/chunk-d28a86c4.cdd32c08.css" rel="prefetch"><link href="/static/css/chunk-da9476ec.f1965e2d.css" rel="prefetch"><link href="/static/css/chunk-ee62c128.44bbe779.css" rel="prefetch"><link href="/static/js/chunk-134ec1dc.87638287.js" rel="prefetch"><link href="/static/js/chunk-13b07ca5.11833bcd.js" rel="prefetch"><link href="/static/js/chunk-1653b664.4bba37ff.js" rel="prefetch"><link href="/static/js/chunk-23726328.7a638dfb.js" rel="prefetch"><link href="/static/js/chunk-283aacba.52472391.js" rel="prefetch"><link href="/static/js/chunk-2ca39dde.bfb67629.js" rel="prefetch"><link href="/static/js/chunk-2d0cc2be.71e3fcd8.js" rel="prefetch"><link href="/static/js/chunk-2d2091df.90a98553.js" rel="prefetch"><link href="/static/js/chunk-2d21da1a.707bd994.js" rel="prefetch"><link href="/static/js/chunk-2d237d41.b4b87abb.js" rel="prefetch"><link href="/static/js/chunk-2f304dee.649e4dc7.js" rel="prefetch"><link href="/static/js/chunk-3b435dde.bd4904a1.js" rel="prefetch"><link href="/static/js/chunk-487896e7.69cdcafb.js" rel="prefetch"><link href="/static/js/chunk-49211740.e4dea096.js" rel="prefetch"><link href="/static/js/chunk-4dae396b.0ee6bb40.js" rel="prefetch"><link href="/static/js/chunk-5145872a.f0bd0577.js" rel="prefetch"><link href="/static/js/chunk-53e279b3.cf489a46.js" rel="prefetch"><link href="/static/js/chunk-595ffc05.8affd7fe.js" rel="prefetch"><link href="/static/js/chunk-5a1e13e4.287f68a0.js" rel="prefetch"><link href="/static/js/chunk-5d5c4530.f0675a96.js" rel="prefetch"><link href="/static/js/chunk-64076603.2c344ed9.js" rel="prefetch"><link href="/static/js/chunk-675c7703.7c7378cd.js" rel="prefetch"><link href="/static/js/chunk-792fd41e.aca41198.js" rel="prefetch"><link href="/static/js/chunk-7fae0422.0d9be069.js" rel="prefetch"><link href="/static/js/chunk-d22da0c0.da01e99e.js" rel="prefetch"><link href="/static/js/chunk-d28a86c4.d0c1f74e.js" rel="prefetch"><link href="/static/js/chunk-da9476ec.f8c15985.js" rel="prefetch"><link href="/static/js/chunk-ee62c128.c11fb53e.js" rel="prefetch"><link href="/static/css/app.011e7c1b.css" rel="preload" as="style"><link href="/static/css/chunk-vendors.5dad8b00.css" rel="preload" as="style"><link href="/static/js/app.7e139a4f.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.948dc2e5.js" rel="preload" as="script"><link href="/static/css/chunk-vendors.5dad8b00.css" rel="stylesheet"><link href="/static/css/app.011e7c1b.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but platypush doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.948dc2e5.js"></script><script src="/static/js/app.7e139a4f.js"></script></body></html>
|
||||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>platypush</title><link href="/static/css/chunk-134ec1dc.849ccfd5.css" rel="prefetch"><link href="/static/css/chunk-13b07ca5.029dd736.css" rel="prefetch"><link href="/static/css/chunk-1653b664.5b949e24.css" rel="prefetch"><link href="/static/css/chunk-23726328.7e460329.css" rel="prefetch"><link href="/static/css/chunk-2ca39dde.efa1eae8.css" rel="prefetch"><link href="/static/css/chunk-2f304dee.a8a2d99a.css" rel="prefetch"><link href="/static/css/chunk-487896e7.b7730bd4.css" rel="prefetch"><link href="/static/css/chunk-49211740.43a25f0f.css" rel="prefetch"><link href="/static/css/chunk-4dae396b.92b3713e.css" rel="prefetch"><link href="/static/css/chunk-5145872a.197de139.css" rel="prefetch"><link href="/static/css/chunk-595ffc05.678c9c97.css" rel="prefetch"><link href="/static/css/chunk-64076603.e451beea.css" rel="prefetch"><link href="/static/css/chunk-675c7703.75b51be7.css" rel="prefetch"><link href="/static/css/chunk-792fd41e.4d467174.css" rel="prefetch"><link href="/static/css/chunk-7fae0422.c233115f.css" rel="prefetch"><link href="/static/css/chunk-d22da0c0.7c71cffb.css" rel="prefetch"><link href="/static/css/chunk-d28a86c4.cdd32c08.css" rel="prefetch"><link href="/static/css/chunk-da9476ec.f1965e2d.css" rel="prefetch"><link href="/static/css/chunk-ee62c128.44bbe779.css" rel="prefetch"><link href="/static/js/chunk-134ec1dc.87638287.js" rel="prefetch"><link href="/static/js/chunk-13b07ca5.11833bcd.js" rel="prefetch"><link href="/static/js/chunk-1653b664.4bba37ff.js" rel="prefetch"><link href="/static/js/chunk-23726328.7a638dfb.js" rel="prefetch"><link href="/static/js/chunk-2ca39dde.bfb67629.js" rel="prefetch"><link href="/static/js/chunk-2d0b270c.82d7f897.js" rel="prefetch"><link href="/static/js/chunk-2d0c1eb0.2fc91e77.js" rel="prefetch"><link href="/static/js/chunk-2d0cc2be.71e3fcd8.js" rel="prefetch"><link href="/static/js/chunk-2d2091df.90a98553.js" rel="prefetch"><link href="/static/js/chunk-2d21b0dc.465e6abf.js" rel="prefetch"><link href="/static/js/chunk-2d21da1a.707bd994.js" rel="prefetch"><link href="/static/js/chunk-2d231217.5ff519da.js" rel="prefetch"><link href="/static/js/chunk-2d237d41.b4b87abb.js" rel="prefetch"><link href="/static/js/chunk-2f304dee.649e4dc7.js" rel="prefetch"><link href="/static/js/chunk-487896e7.69cdcafb.js" rel="prefetch"><link href="/static/js/chunk-49211740.e4dea096.js" rel="prefetch"><link href="/static/js/chunk-4dae396b.0ee6bb40.js" rel="prefetch"><link href="/static/js/chunk-5145872a.f0bd0577.js" rel="prefetch"><link href="/static/js/chunk-595ffc05.8affd7fe.js" rel="prefetch"><link href="/static/js/chunk-64076603.2c344ed9.js" rel="prefetch"><link href="/static/js/chunk-675c7703.7c7378cd.js" rel="prefetch"><link href="/static/js/chunk-792fd41e.aca41198.js" rel="prefetch"><link href="/static/js/chunk-7fae0422.0d9be069.js" rel="prefetch"><link href="/static/js/chunk-d22da0c0.da01e99e.js" rel="prefetch"><link href="/static/js/chunk-d28a86c4.d0c1f74e.js" rel="prefetch"><link href="/static/js/chunk-da9476ec.f8c15985.js" rel="prefetch"><link href="/static/js/chunk-ee62c128.c11fb53e.js" rel="prefetch"><link href="/static/css/app.a835db3a.css" rel="preload" as="style"><link href="/static/css/chunk-vendors.5dad8b00.css" rel="preload" as="style"><link href="/static/js/app.2721d165.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.948dc2e5.js" rel="preload" as="script"><link href="/static/css/chunk-vendors.5dad8b00.css" rel="stylesheet"><link href="/static/css/app.a835db3a.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but platypush doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.948dc2e5.js"></script><script src="/static/js/app.2721d165.js"></script></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
platypush/backend/http/dist/static/js/app.2721d165.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/app.2721d165.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/js/app.2721d165.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/app.2721d165.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
platypush/backend/http/dist/static/js/chunk-2d0b270c.82d7f897.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-2d0b270c.82d7f897.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0b270c"],{"23b7":function(e,a,n){"use strict";n.r(a);var c=n("7a23"),d=Object(c["K"])("data-v-52effd7c"),t=d((function(e,a,n,d,t,i){var p=Object(c["z"])("Media");return Object(c["r"])(),Object(c["e"])(p,{"plugin-name":"media.mpv"})})),i=n("3951"),p={name:"MediaMpv",components:{Media:i["default"]}};p.render=t,p.__scopeId="data-v-52effd7c";a["default"]=p}}]);
|
||||
//# sourceMappingURL=chunk-2d0b270c.82d7f897.js.map
|
1
platypush/backend/http/dist/static/js/chunk-2d0b270c.82d7f897.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-2d0b270c.82d7f897.js.map
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"sources":["webpack:///./src/components/panels/MediaMpv/Index.vue","webpack:///./src/components/panels/MediaMpv/Index.vue?1b60"],"names":["plugin-name","name","components","Media","render","__scopeId"],"mappings":"8PACE,eAAiC,GAA1BA,cAAY,iB,YAMN,GACbC,KAAM,WACNC,WAAY,CAACC,MAAA,eCNf,EAAOC,OAAS,EAChB,EAAOC,UAAY,kBAEJ","file":"static/js/chunk-2d0b270c.82d7f897.js","sourcesContent":["<template>\n <Media plugin-name=\"media.mpv\" />\n</template>\n\n<script>\nimport Media from '@/components/panels/Media/Index'\n\nexport default {\n name: \"MediaMpv\",\n components: {Media},\n}\n</script>\n\n<style scoped>\n\n</style>\n","import { render } from \"./Index.vue?vue&type=template&id=52effd7c&scoped=true\"\nimport script from \"./Index.vue?vue&type=script&lang=js\"\nexport * from \"./Index.vue?vue&type=script&lang=js\"\nscript.render = render\nscript.__scopeId = \"data-v-52effd7c\"\n\nexport default script"],"sourceRoot":""}
|
2
platypush/backend/http/dist/static/js/chunk-2d0c1eb0.2fc91e77.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-2d0c1eb0.2fc91e77.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0c1eb0"],{"47a8":function(e,a,n){"use strict";n.r(a);var t=n("7a23"),c=Object(t["K"])("data-v-08ab61b7"),d=c((function(e,a,n,c,d,b){var r=Object(t["z"])("Media");return Object(t["r"])(),Object(t["e"])(r,{"plugin-name":"media.mplayer"})})),b=n("3951"),r={name:"MediaMplayer",components:{Media:b["default"]}};r.render=d,r.__scopeId="data-v-08ab61b7";a["default"]=r}}]);
|
||||
//# sourceMappingURL=chunk-2d0c1eb0.2fc91e77.js.map
|
1
platypush/backend/http/dist/static/js/chunk-2d0c1eb0.2fc91e77.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-2d0c1eb0.2fc91e77.js.map
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"sources":["webpack:///./src/components/panels/MediaMplayer/Index.vue","webpack:///./src/components/panels/MediaMplayer/Index.vue?3a6b"],"names":["plugin-name","name","components","Media","render","__scopeId"],"mappings":"8PACE,eAAqC,GAA9BA,cAAY,qB,YAMN,GACbC,KAAM,eACNC,WAAY,CAACC,MAAA,eCNf,EAAOC,OAAS,EAChB,EAAOC,UAAY,kBAEJ","file":"static/js/chunk-2d0c1eb0.2fc91e77.js","sourcesContent":["<template>\n <Media plugin-name=\"media.mplayer\" />\n</template>\n\n<script>\nimport Media from '@/components/panels/Media/Index'\n\nexport default {\n name: \"MediaMplayer\",\n components: {Media},\n}\n</script>\n\n<style scoped>\n\n</style>\n","import { render } from \"./Index.vue?vue&type=template&id=08ab61b7&scoped=true\"\nimport script from \"./Index.vue?vue&type=script&lang=js\"\nexport * from \"./Index.vue?vue&type=script&lang=js\"\nscript.render = render\nscript.__scopeId = \"data-v-08ab61b7\"\n\nexport default script"],"sourceRoot":""}
|
2
platypush/backend/http/dist/static/js/chunk-2d21b0dc.465e6abf.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-2d21b0dc.465e6abf.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d21b0dc"],{bdae:function(e,a,n){"use strict";n.r(a);var c=n("7a23"),d=Object(c["K"])("data-v-9233e214"),t=d((function(e,a,n,d,t,i){var o=Object(c["z"])("Media");return Object(c["r"])(),Object(c["e"])(o,{"plugin-name":"media.vlc"})})),i=n("3951"),o={name:"MediaVlc",components:{Media:i["default"]}};o.render=t,o.__scopeId="data-v-9233e214";a["default"]=o}}]);
|
||||
//# sourceMappingURL=chunk-2d21b0dc.465e6abf.js.map
|
1
platypush/backend/http/dist/static/js/chunk-2d21b0dc.465e6abf.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-2d21b0dc.465e6abf.js.map
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"sources":["webpack:///./src/components/panels/MediaVlc/Index.vue","webpack:///./src/components/panels/MediaVlc/Index.vue?f087"],"names":["plugin-name","name","components","Media","render","__scopeId"],"mappings":"4PACE,eAAiC,GAA1BA,cAAY,iB,YAMN,GACbC,KAAM,WACNC,WAAY,CAACC,MAAA,eCNf,EAAOC,OAAS,EAChB,EAAOC,UAAY,kBAEJ","file":"static/js/chunk-2d21b0dc.465e6abf.js","sourcesContent":["<template>\n <Media plugin-name=\"media.vlc\" />\n</template>\n\n<script>\nimport Media from '@/components/panels/Media/Index'\n\nexport default {\n name: \"MediaVlc\",\n components: {Media},\n}\n</script>\n\n<style scoped>\n\n</style>\n","import { render } from \"./Index.vue?vue&type=template&id=9233e214&scoped=true\"\nimport script from \"./Index.vue?vue&type=script&lang=js\"\nexport * from \"./Index.vue?vue&type=script&lang=js\"\nscript.render = render\nscript.__scopeId = \"data-v-9233e214\"\n\nexport default script"],"sourceRoot":""}
|
2
platypush/backend/http/dist/static/js/chunk-2d231217.5ff519da.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-2d231217.5ff519da.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d231217"],{eede:function(e,a,d){"use strict";d.r(a);var n=d("7a23"),c=Object(n["K"])("data-v-7264d7fc"),t=c((function(e,a,d,c,t,i){var o=Object(n["z"])("Media");return Object(n["r"])(),Object(n["e"])(o,{"plugin-name":"media.omxplayer"})})),i=d("3951"),o={name:"MediaMpv",components:{Media:i["default"]}};o.render=t,o.__scopeId="data-v-7264d7fc";a["default"]=o}}]);
|
||||
//# sourceMappingURL=chunk-2d231217.5ff519da.js.map
|
1
platypush/backend/http/dist/static/js/chunk-2d231217.5ff519da.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-2d231217.5ff519da.js.map
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"sources":["webpack:///./src/components/panels/MediaOmxplayer/Index.vue","webpack:///./src/components/panels/MediaOmxplayer/Index.vue?279c"],"names":["plugin-name","name","components","Media","render","__scopeId"],"mappings":"4PACE,eAAuC,GAAhCA,cAAY,uB,YAMN,GACbC,KAAM,WACNC,WAAY,CAACC,MAAA,eCNf,EAAOC,OAAS,EAChB,EAAOC,UAAY,kBAEJ","file":"static/js/chunk-2d231217.5ff519da.js","sourcesContent":["<template>\n <Media plugin-name=\"media.omxplayer\" />\n</template>\n\n<script>\nimport Media from '@/components/panels/Media/Index'\n\nexport default {\n name: \"MediaMpv\",\n components: {Media},\n}\n</script>\n\n<style scoped>\n\n</style>\n","import { render } from \"./Index.vue?vue&type=template&id=7264d7fc&scoped=true\"\nimport script from \"./Index.vue?vue&type=script&lang=js\"\nexport * from \"./Index.vue?vue&type=script&lang=js\"\nscript.render = render\nscript.__scopeId = \"data-v-7264d7fc\"\n\nexport default script"],"sourceRoot":""}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,2 +0,0 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-5d5c4530"],{"524a":function(t,e,n){"use strict";var i=n("7a23"),c=Object(i["K"])("data-v-3cb494ce");Object(i["u"])("data-v-3cb494ce");var o={key:0,class:"col-1 icon"};Object(i["s"])();var s=c((function(t,e,n,c,s,l){return Object(i["r"])(),Object(i["e"])("div",{class:"row item",onClick:e[1]||(e[1]=function(){return l.clicked.apply(l,arguments)})},[n.iconClass?(Object(i["r"])(),Object(i["e"])("div",o,[Object(i["h"])("i",{class:n.iconClass},null,2)])):Object(i["f"])("",!0),Object(i["h"])("div",{class:["text",{"col-11":null!=n.iconClass}],textContent:Object(i["C"])(n.text)},null,10,["textContent"])])})),l={name:"DropdownItem",props:{iconClass:{type:String},text:{type:String},disabled:{type:Boolean,default:!1}},methods:{clicked:function(t){this.$parent.$emit("click",t),this.$parent.visible=!1}}};n("c9a1");l.render=s,l.__scopeId="data-v-3cb494ce";e["a"]=l},5769:function(t,e,n){},"64b0":function(t,e,n){},"87ac":function(t,e,n){"use strict";n("5769")},ab0f:function(t,e,n){"use strict";var i=n("7a23"),c=Object(i["K"])("data-v-00fa59b4");Object(i["u"])("data-v-00fa59b4");var o={class:"dropdown-container",ref:"container"};Object(i["s"])();var s=c((function(t,e,n,c,s,l){return Object(i["r"])(),Object(i["e"])("div",o,[Object(i["h"])("button",{title:n.title,ref:"button",onClick:e[1]||(e[1]=Object(i["J"])((function(t){return l.toggle(t)}),["stop"]))},[n.iconClass?(Object(i["r"])(),Object(i["e"])("i",{key:0,class:["icon",n.iconClass]},null,2)):Object(i["f"])("",!0),n.text?(Object(i["r"])(),Object(i["e"])("span",{key:1,class:"text",textContent:Object(i["C"])(n.text)},null,8,["textContent"])):Object(i["f"])("",!0)],8,["title"]),Object(i["h"])("div",{class:["dropdown fade-in",{hidden:!s.visible}],id:n.id,ref:"dropdown"},[Object(i["y"])(t.$slots,"default")],10,["id"])],512)})),l={name:"Dropdown",emits:["click"],props:{id:{type:String},items:{type:Array,default:function(){return[]}},iconClass:{type:String,default:"fa fa-ellipsis-h"},text:{type:String},title:{type:String}},data:function(){return{visible:!1}},methods:{documentClickHndl:function(t){if(this.visible){var e=t.target;while(e){if(!this.$refs.dropdown)break;if(e===this.$refs.dropdown.element)return;e=e.parentElement}this.close()}},close:function(){this.visible=!1,document.removeEventListener("click",this.documentClickHndl)},open:function(){var t=this;document.addEventListener("click",this.documentClickHndl),this.visible=!0,setTimeout((function(){var e=t.$refs.dropdown;e.style.left=0,e.style.top=parseFloat(getComputedStyle(t.$refs.button).height)+"px",e.getBoundingClientRect().left>window.innerWidth/2&&(e.style.left=-e.clientWidth+parseFloat(getComputedStyle(t.$refs.button).width)+"px"),e.getBoundingClientRect().top>window.innerHeight/2&&(e.style.top=-e.clientHeight+parseFloat(getComputedStyle(t.$refs.button).height)+"px")}),10)},toggle:function(t){t.stopPropagation(),this.$emit("click"),this.visible?this.close():this.open()}}};n("87ac");l.render=s,l.__scopeId="data-v-00fa59b4";e["a"]=l},c9a1:function(t,e,n){"use strict";n("64b0")}}]);
|
||||
//# sourceMappingURL=chunk-5d5c4530.f0675a96.js.map
|
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import Api from "@/utils/Api";
|
||||
import Cookies from "@/utils/Cookies";
|
||||
import DateTime from "@/utils/DateTime";
|
||||
import Events from "@/utils/Events";
|
||||
import Notification from "@/utils/Notification";
|
||||
|
@ -8,6 +9,6 @@ import Types from "@/utils/Types";
|
|||
|
||||
export default {
|
||||
name: "Utils",
|
||||
mixins: [Api, Notification, Events, DateTime, Screen, Types],
|
||||
mixins: [Api, Cookies, Notification, Events, DateTime, Screen, Types],
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -74,6 +74,10 @@ export default {
|
|||
this.isVisible = false
|
||||
},
|
||||
|
||||
hide() {
|
||||
this.close()
|
||||
},
|
||||
|
||||
show() {
|
||||
this.prevVisible = this.isVisible
|
||||
this.isVisible = true
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<span class="hostname" v-if="hostname" v-text="hostname" />
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<ul class="plugins">
|
||||
<li v-for="name in Object.keys(panels).sort()" :key="name" class="entry" :class="{selected: name === selectedPanel}"
|
||||
:title="name" @click="onItemClick(name)">
|
||||
<a :href="`/#${name}`">
|
||||
|
@ -18,6 +18,28 @@
|
|||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="footer">
|
||||
<li :class="{selected: selectedPanel === 'settings'}" title="Settings" @click="onItemClick('settings')">
|
||||
<!--suppress HtmlUnknownAnchorTarget -->
|
||||
<a href="/#settings">
|
||||
<span class="icon">
|
||||
<i class="fa fa-cog" />
|
||||
</span>
|
||||
<span class="name" v-if="!collapsed">Settings</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li title="Logout" @click="onItemClick('logout')">
|
||||
<!--suppress HtmlUnknownTarget -->
|
||||
<a href="/logout">
|
||||
<span class="icon">
|
||||
<i class="fas fa-sign-out-alt" />
|
||||
</span>
|
||||
<span class="name" v-if="!collapsed">Logout</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
|
@ -68,6 +90,10 @@ export default {
|
|||
|
||||
<!--suppress SassScssResolvedByNameOnly -->
|
||||
<style lang="scss" scoped>
|
||||
$toggler-height: 2em;
|
||||
$footer-collapsed-height: 4em;
|
||||
$footer-expanded-height: 8.25em;
|
||||
|
||||
nav {
|
||||
@media screen and (max-width: $tablet) {
|
||||
width: 100%;
|
||||
|
@ -150,6 +176,18 @@ nav {
|
|||
}
|
||||
}
|
||||
|
||||
.plugins {
|
||||
height: calc(100% - #{$toggler-height} - #{$footer-expanded-height} - .75em);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
height: $footer-expanded-height;
|
||||
background: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -180,14 +218,31 @@ nav {
|
|||
}
|
||||
|
||||
.toggler {
|
||||
height: $toggler-height;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.plugins {
|
||||
height: calc(100% - #{$toggler-height} - #{$footer-collapsed-height});
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
height: $footer-collapsed-height;
|
||||
padding: 0;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
.footer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
flex-grow: 1;
|
||||
|
||||
li {
|
||||
box-shadow: none;
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
<template>
|
||||
<div class="settings-container">
|
||||
<header>
|
||||
<div class="col-8">
|
||||
<select title="View" @change="selectedView = $event.target.value">
|
||||
<option value="users" :selected="selectedView === 'users'">Users</option>
|
||||
<option value="token" :selected="selectedView === 'token'">Generate Token</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-4 pull-right">
|
||||
<button title="Add User" @click="$refs.usersView.$refs.addUserModal.show()" v-if="selectedView === 'users'">
|
||||
<i class="fa fa-plus" />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<Users :session-token="sessionToken" :current-user="currentUser"
|
||||
v-if="selectedView === 'users'" ref="usersView" />
|
||||
<Token :session-token="sessionToken" :current-user="currentUser"
|
||||
v-else-if="selectedView === 'token'" ref="tokenView" />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Token from "@/components/panels/Settings/Token";
|
||||
import Users from "@/components/panels/Settings/Users";
|
||||
import Utils from "@/Utils";
|
||||
|
||||
export default {
|
||||
name: "Settings",
|
||||
components: {Users, Token},
|
||||
mixins: [Utils],
|
||||
|
||||
data() {
|
||||
return {
|
||||
selectedView: 'users',
|
||||
currentUser: null,
|
||||
sessionToken: null,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async refresh() {
|
||||
this.sessionToken = this.getCookies()['session_token']
|
||||
this.currentUser = await this.request('user.get_user_by_session', {session_token: this.sessionToken})
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.refresh()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
$header-height: 3em;
|
||||
|
||||
.settings-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
header {
|
||||
width: 100%;
|
||||
height: $header-height;
|
||||
display: flex;
|
||||
background: $background-color;
|
||||
box-shadow: $border-shadow-bottom;
|
||||
padding: .5em;
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
padding-top: .25em;
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
height: calc(100% - #{$header-height});
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
button {
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
border: none;
|
||||
color: $default-hover-fg;
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
|
||||
input {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
input[type=password] {
|
||||
border-radius: 1em;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,234 @@
|
|||
<template>
|
||||
<div class="token-container">
|
||||
<Loading v-if="loading" />
|
||||
|
||||
<Modal ref="tokenModal">
|
||||
<div class="token-container">
|
||||
<label>
|
||||
This is your generated token. Treat it carefully and do not share it with untrusted parties.<br/>
|
||||
Also, make sure to save it - it WILL NOT be displayed again.
|
||||
|
||||
<textarea class="token" v-text="token" @focus="onTokenSelect" />
|
||||
</label>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<div class="body">
|
||||
<div class="description">
|
||||
<p>Generate a JWT authentication token that can be used for API calls to the <tt>/execute</tt> endpoint.</p><br/>
|
||||
<p>You can include the token in your requests in any of the following ways:</p>
|
||||
|
||||
<ul>
|
||||
<li>Specify it on the <tt>Authorization: Bearer</tt> header;</li>
|
||||
<li>Specify it on the <tt>X-Token</tt> header;</li>
|
||||
<li>Specify it as a URL parameter: <tt>http://site:8008/execute?token=...</tt>;</li>
|
||||
<li>Specify it on the body of your JSON request: <tt>{"type":"request", "action", "...", "token":"..."}</tt>.</li>
|
||||
</ul>
|
||||
|
||||
Confirm your credentials in order to generate a new token.
|
||||
</div>
|
||||
|
||||
<div class="form-container">
|
||||
<form @submit.prevent="generateToken" ref="generateTokenForm">
|
||||
<label>
|
||||
Username
|
||||
<input type="text" name="username" :value="currentUser.username" disabled>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Password
|
||||
<input type="password" name="password">
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Token validity in days
|
||||
<input type="text" name="validityDays">
|
||||
<span class="note">
|
||||
Decimal values are also supported (e.g. <i>0.5</i> to identify 6 hours). An empty or zero value means that
|
||||
the token has no expiry date.
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<input type="submit" value="Generate token">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import Loading from "@/components/Loading";
|
||||
import Utils from "@/Utils";
|
||||
import Modal from "@/components/Modal";
|
||||
|
||||
export default {
|
||||
name: "Token",
|
||||
components: {Modal, Loading},
|
||||
mixins: [Utils],
|
||||
|
||||
props: {
|
||||
currentUser: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
token: null,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async generateToken(event) {
|
||||
const username = this.currentUser.username
|
||||
const password = event.target.password.value
|
||||
let validityDays = event.target.validityDays?.length ? parseInt(event.target.validityDays.value) : 0
|
||||
if (!validityDays)
|
||||
validityDays = null
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
this.token = (await axios.post('/auth', {
|
||||
username: username,
|
||||
password: password,
|
||||
expiry_days: validityDays,
|
||||
})).data.token
|
||||
|
||||
if (this.token?.length)
|
||||
this.$refs.tokenModal.show()
|
||||
} catch (e) {
|
||||
console.error(e.toString())
|
||||
this.notify({
|
||||
text: e.toString(),
|
||||
error: true,
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
onTokenSelect(event) {
|
||||
event.target.select()
|
||||
document.execCommand('copy')
|
||||
|
||||
this.notify({
|
||||
text: 'Token copied to clipboard',
|
||||
image: {
|
||||
iconClass: 'fa fa-check',
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.token-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
margin-top: .15em;
|
||||
|
||||
.body {
|
||||
background: $background-color;
|
||||
display: flex;
|
||||
|
||||
.description {
|
||||
text-align: left;
|
||||
padding: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 1em .5em;
|
||||
|
||||
li {
|
||||
list-style: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.form-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
form {
|
||||
max-width: 250pt;
|
||||
|
||||
.note {
|
||||
display: block;
|
||||
font-size: .75em;
|
||||
margin: -.75em 0 2em 0;
|
||||
}
|
||||
}
|
||||
|
||||
input[type=password] {
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
.modal {
|
||||
.content {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.body {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.token-container {
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 10em;
|
||||
margin-top: 1em;
|
||||
border-radius: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: calc(#{$desktop} - 1px)) {
|
||||
.token-container {
|
||||
.body {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.form-container {
|
||||
justify-content: center;
|
||||
box-shadow: $border-shadow-top;
|
||||
margin-top: -1em;
|
||||
padding-top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: $desktop) {
|
||||
.token-container {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.description {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
width: 50%;
|
||||
justify-content: right;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.body {
|
||||
max-width: 650pt;
|
||||
flex-direction: row;
|
||||
justify-content: left;
|
||||
margin-top: 1.5em;
|
||||
border-radius: 1em;
|
||||
border: $default-border-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,313 @@
|
|||
<template>
|
||||
<Loading v-if="loading" />
|
||||
|
||||
<Modal ref="addUserModal" title="Add User">
|
||||
<form action="#" method="POST" ref="addUserForm" @submit="createUser">
|
||||
<label>
|
||||
<input type="text" name="username" placeholder="Username" :disabled="commandRunning">
|
||||
</label>
|
||||
<label>
|
||||
<input type="password" name="password" placeholder="Password" :disabled="commandRunning">
|
||||
</label>
|
||||
<label>
|
||||
<input type="password" name="confirm_password" placeholder="Confirm password" :disabled="commandRunning">
|
||||
</label>
|
||||
|
||||
<input type="submit" value="Create User" :disabled="commandRunning">
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
<Modal ref="changePasswordModal" title="Change Password">
|
||||
<form action="#" method="POST" ref="changePasswordForm" @submit="changePassword">
|
||||
<label>
|
||||
<input type="text" name="username" placeholder="Username" :value="selectedUser" disabled="disabled">
|
||||
</label>
|
||||
<label>
|
||||
<input type="password" name="password" placeholder="Current password" :disabled="commandRunning">
|
||||
</label>
|
||||
<label>
|
||||
<input type="password" name="new_password" placeholder="New password" :disabled="commandRunning">
|
||||
</label>
|
||||
<label>
|
||||
<input type="password" name="confirm_new_password" placeholder="Confirm new password" :disabled="commandRunning">
|
||||
</label>
|
||||
<input type="submit" value="Change Password" :disabled="commandRunning">
|
||||
</form>
|
||||
</modal>
|
||||
|
||||
<div class="body">
|
||||
<ul class="users-list">
|
||||
<li v-for="user in users" :key="user.user_id" class="item user" @click="selectedUser = user.username">
|
||||
<div class="name col-8" v-text="user.username" />
|
||||
<div class="actions pull-right col-4">
|
||||
<Dropdown title="User Actions" icon-class="fa fa-cog">
|
||||
<DropdownItem text="Change Password" :disabled="commandRunning" icon-class="fa fa-key"
|
||||
@click="selectedUser = user.username; $refs.changePasswordModal.show()" />
|
||||
<DropdownItem text="Delete User" :disabled="commandRunning" icon-class="fa fa-trash"
|
||||
@click="deleteUser(user)" />
|
||||
</Dropdown>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dropdown from "@/components/elements/Dropdown";
|
||||
import Modal from "@/components/Modal";
|
||||
import Loading from "@/components/Loading";
|
||||
import Utils from "@/Utils";
|
||||
import DropdownItem from "@/components/elements/DropdownItem";
|
||||
|
||||
export default {
|
||||
name: "Users",
|
||||
components: {DropdownItem, Loading, Modal, Dropdown},
|
||||
mixins: [Utils],
|
||||
|
||||
props: {
|
||||
sessionToken: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
currentUser: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
users: [],
|
||||
commandRunning: false,
|
||||
loading: false,
|
||||
selectedUser: null,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async refresh() {
|
||||
this.loading = true
|
||||
try {
|
||||
this.users = await this.request('user.get_users')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
async createUser(event) {
|
||||
event.preventDefault()
|
||||
|
||||
const form = [...this.$refs.addUserForm.querySelectorAll('input[name]')].reduce((map, input) => {
|
||||
map[input.name] = input.value
|
||||
return map
|
||||
}, {})
|
||||
|
||||
if (form.password !== form.confirm_password) {
|
||||
this.notify({
|
||||
title: 'Unable to create user',
|
||||
text: 'Please check that the passwords match',
|
||||
error: true,
|
||||
image: {
|
||||
iconClass: 'fas fa-times',
|
||||
},
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.commandRunning = true
|
||||
try {
|
||||
await this.request('user.create_user', {
|
||||
username: form.username,
|
||||
password: form.password,
|
||||
session_token: this.sessionToken,
|
||||
})
|
||||
} finally {
|
||||
this.commandRunning = false
|
||||
}
|
||||
|
||||
this.notify({
|
||||
text: 'User ' + form.username + ' created',
|
||||
image: {
|
||||
iconClass: 'fas fa-check',
|
||||
},
|
||||
})
|
||||
|
||||
this.$refs.addUserModal.close()
|
||||
await this.refresh()
|
||||
},
|
||||
|
||||
// onTokenFocus(event) {
|
||||
// event.target.select()
|
||||
// this.document.execCommand('copy')
|
||||
// event.target.setAttribute('disabled', true)
|
||||
//
|
||||
// this.notify({
|
||||
// text: 'Token copied to the clipboard',
|
||||
// image: {
|
||||
// iconClass: 'fas fa-copy',
|
||||
// },
|
||||
// })
|
||||
// },
|
||||
//
|
||||
// onTokenBlur(event) {
|
||||
// event.target.select()
|
||||
// this.document.execCommand('copy')
|
||||
// event.target.removeAttribute('disabled')
|
||||
//
|
||||
// this.notify({
|
||||
// text: 'Token copied to clipboard',
|
||||
// image: {
|
||||
// iconClass: 'fas fa-copy',
|
||||
// },
|
||||
// })
|
||||
// },
|
||||
|
||||
async changePassword(event) {
|
||||
event.preventDefault()
|
||||
|
||||
const form = [...this.$refs.changePasswordForm.querySelectorAll('input[name]')].reduce((map, input) => {
|
||||
map[input.name] = input.value
|
||||
return map
|
||||
}, {})
|
||||
|
||||
if (form.new_password !== form.confirm_new_password) {
|
||||
this.notify({
|
||||
title: 'Unable to update password',
|
||||
text: 'Please check that the passwords match',
|
||||
error: true,
|
||||
image: {
|
||||
iconClass: 'fas fa-times',
|
||||
},
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.commandRunning = true
|
||||
let success = false
|
||||
|
||||
try {
|
||||
success = await this.request('user.update_password', {
|
||||
username: form.username,
|
||||
old_password: form.password,
|
||||
new_password: form.new_password,
|
||||
})
|
||||
} finally {
|
||||
this.commandRunning = false
|
||||
}
|
||||
|
||||
if (success) {
|
||||
this.$refs.changePasswordModal.close()
|
||||
this.notify({
|
||||
text: 'Password successfully updated',
|
||||
image: {
|
||||
iconClass: 'fas fa-check',
|
||||
},
|
||||
})
|
||||
} else {
|
||||
this.notify({
|
||||
title: 'Unable to update password',
|
||||
text: 'The current password is incorrect',
|
||||
error: true,
|
||||
image: {
|
||||
iconClass: 'fas fa-times',
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
async deleteUser(user) {
|
||||
if (!confirm('Are you sure that you want to remove the user ' + user.username + '?'))
|
||||
return
|
||||
|
||||
this.commandRunning = true
|
||||
try {
|
||||
await this.request('user.delete_user', {
|
||||
username: user.username,
|
||||
session_token: this.sessionToken,
|
||||
})
|
||||
} finally {
|
||||
this.commandRunning = false
|
||||
}
|
||||
|
||||
this.notify({
|
||||
text: 'User ' + user.username + ' removed',
|
||||
image: {
|
||||
iconClass: 'fas fa-check',
|
||||
},
|
||||
})
|
||||
|
||||
await this.refresh()
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.refresh()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.settings-container {
|
||||
.body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal {
|
||||
.body {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.users-list {
|
||||
background: $background-color;
|
||||
margin-top: .15em;
|
||||
height: max-content;
|
||||
|
||||
.user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .75em;
|
||||
box-shadow: $border-shadow-bottom;
|
||||
|
||||
&:hover {
|
||||
background: $hover-bg;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: inline-flex;
|
||||
justify-content: right;
|
||||
|
||||
button {
|
||||
width: min-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $desktop) {
|
||||
.users-list {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: $desktop) {
|
||||
.users-list {
|
||||
min-width: 400pt;
|
||||
max-width: 600pt;
|
||||
margin-top: 1em;
|
||||
border-radius: 1em;
|
||||
box-shadow: $border-shadow-bottom;
|
||||
|
||||
.user {
|
||||
border-radius: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
14
platypush/backend/http/webapp/src/utils/Cookies.vue
Normal file
14
platypush/backend/http/webapp/src/utils/Cookies.vue
Normal file
|
@ -0,0 +1,14 @@
|
|||
<script>
|
||||
export default {
|
||||
name: "Cookies",
|
||||
methods: {
|
||||
getCookies() {
|
||||
return document.cookie.split(/;\s*/).reduce((obj, item) => {
|
||||
const [k, v] = item.split('=')
|
||||
obj[k] = v
|
||||
return obj
|
||||
}, {})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -98,10 +98,15 @@ form {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
input[type=submit] {
|
||||
input[type=submit],
|
||||
input[type=password] {
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
input[type=password] {
|
||||
padding: .25em .5em;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
display: flex;
|
||||
font-size: 0.8em;
|
||||
|
|
|
@ -4,7 +4,13 @@
|
|||
<Nav :panels="components" :selected-panel="selectedPanel" :hostname="hostname"
|
||||
@select="selectedPanel = $event" v-else />
|
||||
|
||||
<div class="canvas">
|
||||
<div class="canvas" v-if="selectedPanel === 'settings'">
|
||||
<div class="panel">
|
||||
<Settings />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="canvas" v-else>
|
||||
<div class="panel" :class="{hidden: name !== selectedPanel}" v-for="(panel, name) in components" :key="name">
|
||||
<component :is="panel.component" :config="panel.config" :plugin-name="name" v-if="name === selectedPanel" />
|
||||
</div>
|
||||
|
@ -17,11 +23,12 @@ import {defineAsyncComponent} from "vue";
|
|||
import Utils from '@/Utils'
|
||||
import Loading from "@/components/Loading";
|
||||
import Nav from "@/components/Nav";
|
||||
import Settings from "@/components/panels/Settings/Index";
|
||||
|
||||
export default {
|
||||
name: 'Panel',
|
||||
mixins: [Utils],
|
||||
components: {Nav, Loading},
|
||||
components: {Settings, Nav, Loading},
|
||||
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -18,6 +18,14 @@ module.exports = {
|
|||
'/execute': {
|
||||
target: 'http://localhost:8008',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/auth': {
|
||||
target: 'http://localhost:8008',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/logout': {
|
||||
target: 'http://localhost:8008',
|
||||
changeOrigin: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from typing import List, Dict, Any
|
||||
|
||||
from platypush.plugins import Plugin, action
|
||||
from platypush.user import UserManager
|
||||
|
||||
|
@ -146,5 +148,64 @@ class UserPlugin(Plugin):
|
|||
|
||||
return self.user_manager.delete_user_session(session_token)
|
||||
|
||||
@action
|
||||
def get_users(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get the list of registered users.
|
||||
:return:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"user_id": 1,
|
||||
"username": "user1",
|
||||
"created_at": "2020-11-26T22:41:40.550574"
|
||||
},
|
||||
{
|
||||
"user_id": 2,
|
||||
"username": "user2",
|
||||
"created_at": "2020-11-28T21:10:23.224813"
|
||||
}
|
||||
]
|
||||
|
||||
"""
|
||||
return [
|
||||
{
|
||||
'user_id': user.user_id,
|
||||
'username': user.username,
|
||||
'created_at': user.created_at.isoformat(),
|
||||
}
|
||||
for user in self.user_manager.get_users().all()
|
||||
]
|
||||
|
||||
@action
|
||||
def get_user_by_session(self, session_token: str) -> dict:
|
||||
"""
|
||||
Get the user record associated to a session token.
|
||||
|
||||
:param session_token: Session token.
|
||||
:return:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"user_id": 1,
|
||||
"username": "user1",
|
||||
"created_at": "2020-11-26T22:41:40.550574"
|
||||
}
|
||||
]
|
||||
|
||||
"""
|
||||
user = self.user_manager.get_user_by_session(session_token)
|
||||
assert user, 'No user associated with the specified session token'
|
||||
|
||||
return {
|
||||
'user_id': user.user_id,
|
||||
'username': user.username,
|
||||
'created_at': user.created_at.isoformat(),
|
||||
}
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
|
@ -161,6 +161,23 @@ class UserManager:
|
|||
rand = bytes(random.randint(0, 255) for _ in range(0, 255))
|
||||
return hashlib.sha256(rand).hexdigest()
|
||||
|
||||
def get_user_by_session(self, session_token: str):
|
||||
"""
|
||||
Get a user associated to a session token.
|
||||
|
||||
:param session_token: Session token.
|
||||
"""
|
||||
session = self._get_db_session()
|
||||
return session.query(User).join(UserSession).filter_by(session_token=session_token).first()
|
||||
if not user:
|
||||
return None
|
||||
|
||||
return {
|
||||
'user_id': user.user_id,
|
||||
'username': user.username,
|
||||
'created_at': user.created_at,
|
||||
}
|
||||
|
||||
def generate_jwt_token(self, username: str, password: str, expires_at: Optional[datetime.datetime] = None) -> str:
|
||||
"""
|
||||
Create a user JWT token for API usage.
|
||||
|
|
Loading…
Reference in a new issue