From da57c38dddf7b02a27a9d81e2ebd14982b676cad Mon Sep 17 00:00:00 2001 From: jr-k Date: Thu, 11 Jul 2024 20:52:51 +0200 Subject: [PATCH] players explorer ok --- data/www/css/compiled/main.css | 2 +- data/www/css/lib/jquery-explr-1.4.css | 14 +- data/www/js/fleet/node-players-old.js | 110 ++++++++++ data/www/js/fleet/node-players.js | 180 ++++++++-------- data/www/js/slideshow/contents.js | 12 +- data/www/scss/main.scss | 1 + data/www/scss/pages/_node_player.scss | 22 ++ lang/en.json | 14 +- lang/es.json | 14 +- lang/fr.json | 14 +- lang/it.json | 14 +- src/controller/ContentController.py | 95 +++++---- src/controller/FleetNodePlayerController.py | 188 ++++++++++++----- src/manager/ContentManager.py | 2 +- src/manager/FolderManager.py | 27 ++- src/manager/NodePlayerManager.py | 66 +++--- src/manager/VariableManager.py | 1 + src/model/entity/NodePlayer.py | 48 ++--- src/model/enum/ContentType.py | 4 +- src/model/enum/OperatingSystem.py | 37 ++++ views/fleet/node-players/edit.jinja.html | 105 ++++++++++ views/fleet/node-players/list.jinja.html | 195 ++++++++++++++++++ views/fleet/node-players/modal/add.jinja.html | 47 +++++ views/fleet/player/component/table.jinja.html | 67 ------ views/fleet/player/list.jinja.html | 73 ------- views/fleet/player/modal/add.jinja.html | 42 ---- views/fleet/player/modal/edit.jinja.html | 44 ---- views/slideshow/contents/list.jinja.html | 29 +-- .../slides/component/table.jinja.html | 4 +- 29 files changed, 965 insertions(+), 506 deletions(-) create mode 100644 data/www/js/fleet/node-players-old.js create mode 100644 data/www/scss/pages/_node_player.scss create mode 100644 src/model/enum/OperatingSystem.py create mode 100644 views/fleet/node-players/edit.jinja.html create mode 100644 views/fleet/node-players/list.jinja.html create mode 100644 views/fleet/node-players/modal/add.jinja.html delete mode 100644 views/fleet/player/component/table.jinja.html delete mode 100644 views/fleet/player/list.jinja.html delete mode 100644 views/fleet/player/modal/add.jinja.html delete mode 100644 views/fleet/player/modal/edit.jinja.html diff --git a/data/www/css/compiled/main.css b/data/www/css/compiled/main.css index 1ccaaa8..be81643 100644 --- a/data/www/css/compiled/main.css +++ b/data/www/css/compiled/main.css @@ -1 +1 @@ -.info{color:#027bff!important}.bg-info{background-color:#027bff!important}.border-info{border-color:#027bff!important}.success{color:#0eef5f!important}.bg-success{background-color:#0eef5f!important}.border-success{border-color:#0eef5f!important}.success-alt{color:#11a948!important}.bg-success-alt{background-color:#11a948!important}.border-success-alt{border-color:#11a948!important}.danger{color:#ef0e5d!important}.bg-danger{background-color:#ef0e5d!important}.border-danger{border-color:#ef0e5d!important}.danger-alt{color:#c20941!important}.bg-danger-alt{background-color:#c20941!important}.border-danger-alt{border-color:#c20941!important}.purple{color:#bc48ff!important}.bg-purple{background-color:#bc48ff!important}.border-purple{border-color:#bc48ff!important}.purple-alt{color:#692fbd!important}.bg-purple-alt{background-color:#692fbd!important}.border-purple-alt{border-color:#692fbd!important}.youtube{color:#fd3c01!important}.bg-youtube{background-color:#fd3c01!important}.border-youtube{border-color:#fd3c01!important}.neutral{color:#464646!important}.bg-neutral{background-color:#464646!important}.border-neutral{border-color:#464646!important}.white{color:#fff!important}.bg-white{background-color:#fff!important}.border-white{border-color:#fff!important}.black{color:#000!important}.bg-black{background-color:#000!important}.border-black{border-color:#000!important}@font-face{font-family:Sixtyfour;src:url(../../webfonts/Sixtyfour-Regular.ttf) format("truetype")}*{font-family:Roboto,Arial,"sans-serif";margin:0;padding:0;box-sizing:border-box}html{background-color:#111}body,html{height:100%;font-family:Arial,sans-serif}.container{display:flex;height:100vh}.horizontal{display:flex;flex-direction:row;justify-content:flex-start;align-items:flex-start;flex:1;align-self:stretch}main{flex:1;display:flex;flex-direction:column}main .main-container{display:flex;flex-direction:column;flex:1;overflow:hidden;align-self:stretch}main .main-container .top-content{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;align-self:stretch;padding:10px 10px 10px 20px;background:transparent;border-bottom:1px solid #222}main .main-container .top-content h1{color:#fff;font-weight:600;font-size:24px}main .main-container .top-content .top-actions{flex:1;display:flex;flex-direction:row;justify-content:flex-end;align-items:center}main .main-container .top-content .top-actions button{margin-left:10px}main .main-container .bottom-content{display:flex;flex-direction:row;align-self:stretch;justify-content:flex-start;align-items:flex-start;flex:1;overflow-y:auto;background:radial-gradient(circle at 0% 53%,rgba(239,14,93,.8) 10%,transparent 45%),radial-gradient(circle at 135% 53%,rgba(2,123,255,.8) 10%,transparent 95%),radial-gradient(circle at 50% 80%,rgba(14,239,95,.8) 40%,transparent 95%)}main .main-container .bottom-content .page-content{flex:2;overflow-y:auto;align-self:stretch;display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start;background:#000c;padding:5px}main .main-container .bottom-content .page-content .inner{flex:1;display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start;padding:0 10px 40px;background:#111;align-self:stretch}main .main-container .bottom-content .page-panel{flex:1;overflow-y:auto;align-self:stretch;background:#111;border-top:none}main .main-container .bottom-content .page-panel.left-panel{border-right:1px solid #222;border-left:none}main .main-container .bottom-content .page-panel.left-panel.explr-explorer{flex:.5;overflow-y:auto;padding:0;background:#111;box-shadow:1px 1px .5px .5px inset #0003;max-width:250px}main .main-container .bottom-content .page-panel.right-panel{border-left:1px solid #222;border-right:none}.invisible{visibility:hidden!important}.hidden{display:none!important}.tac{text-align:center!important}.tar{text-align:right!important}a{text-decoration:none}.normal{font-weight:400!important}.bold{font-weight:700!important}.col{display:flex;flex:1;flex-direction:column;align-self:stretch}main .context-bar{padding:10px;position:sticky;top:0;z-index:1000;max-height:80px;border-bottom:1px solid #222;display:flex;flex-direction:row;align-items:center}main .context-bar .context-menu{flex:1}main .context-bar .context-menu .inner{display:flex}main .context-bar .context-menu .inner ul.pills{margin:0}main .context-bar .context-divider{width:1px;height:100%;background:#222;margin-left:20px;margin-right:20px}main .context-bar .context-user{display:flex;margin-right:20px}main .context-bar .context-user .trigger{color:#fff}main .context-bar .context-user .trigger .avatar{width:32px;height:32px;border-radius:4px;background:#027bff;margin-right:10px;display:flex;flex-direction:row;justify-content:center;align-items:center;text-align:center;font-weight:700;font-size:14px;border:1px solid #444}main .context-bar .context-user .trigger i{margin-top:-5px;margin-left:10px}menu{width:300px;background:#111;overflow-y:auto;overflow-x:visible;padding:20px;z-index:2000;display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start;border-right:1px solid #222;min-width:64px}menu h1.logo{margin:40px 0 0 10px;align-self:stretch;display:flex}menu h1.logo a{text-align:center;text-shadow:0px 0 0 #fff,0px 2px 0 #444,0 0px 0 rgb(14,239,95),-0px 0 0 rgb(2,123,255),0 -0px 0 rgb(239,14,93);text-decoration:none;background:linear-gradient(90deg,#a0a0a0 0,#bebebe 46%,#dcdcdc);-webkit-background-clip:text;color:transparent;flex:1;font-family:Sixtyfour,Work Sans,Arial,"sans-serif";align-self:stretch;padding-right:3px;font-size:20px;text-transform:uppercase;transition:all .55s cubic-bezier(.19,1,.22,1);display:flex;flex-direction:row;justify-content:center;align-items:center;position:relative}menu h1.logo a img{flex-shrink:0;width:30px;margin-right:10px;position:absolute;left:5px;transition:all .55s cubic-bezier(.19,1,.22,1)}menu h1.logo a img.after{opacity:0}menu:hover h1.logo a{text-align:center;text-shadow:3px 0 0 #fff,3px 2px 0 #444,0 3px 0 rgb(14,239,95),-3px 0 0 rgb(2,123,255),0 -3px 0 rgb(239,14,93);text-decoration:none;background:linear-gradient(90deg,#a0a0a0 0,#bebebe 46%,#dcdcdc);-webkit-background-clip:text;color:transparent}menu:hover h1.logo a img.before{opacity:0}menu:hover h1.logo a img.after{animation-duration:.2s;animation-name:logotouch}menu nav{display:flex;align-self:stretch;flex:1}menu nav ul{margin:60px 0 20px;flex:1;align-self:flex-start;display:flex;flex-direction:column;list-style:none}menu nav ul li{align-self:stretch;overflow:hidden;position:relative;transition:all .55s cubic-bezier(.19,1,.22,1);margin:10px 0;border-radius:4px}menu nav ul li a{color:#ffffffe6;font-size:16px;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;align-self:stretch;flex:1;padding-top:5px;padding-bottom:5px;padding-left:10px}menu nav ul li a i{color:#fff;opacity:.2;background:transparent;display:flex;justify-content:center;align-items:center;align-self:stretch;padding:10px;width:40px;border-radius:4px;text-align:center;margin-right:20px}menu nav ul li:after{background:#fff;content:"";height:195px;left:-200px;opacity:.2;position:absolute;top:-50px;transform:rotate(35deg);transition:all .55s cubic-bezier(.19,1,.22,1);width:50px;z-index:-2;cursor:pointer}menu nav ul li.active a{color:#027bff;font-weight:700}menu nav ul li.active a i{opacity:1;background:#ffffffe6;background:#017bff}menu nav ul li:hover{background:#027bff}menu nav ul li:hover:after{z-index:2;left:120%;transition:all .55s cubic-bezier(.19,1,.22,1)}menu nav ul li:hover a{color:#fff;font-weight:700}menu nav ul li:hover a i{color:#fff;opacity:1}menu footer{background:#ffffff03;padding:20px 0;display:flex;flex-direction:row;align-self:stretch;text-align:center;justify-content:center}menu footer p{color:#444}menu footer p.version a{color:#777;font-weight:700}.dropdown{position:relative;display:flex;align-self:stretch}.dropdown.dropdown-show ul.dropdown-menu{display:flex;flex-direction:column}.dropdown .trigger{cursor:pointer;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;align-self:stretch;flex:1}.dropdown ul.dropdown-menu{position:absolute;top:100%;left:0;display:none;background-color:#222;box-shadow:0 8px 16px #0003;z-index:1000;list-style-type:none;margin:0;overflow:hidden;border-radius:4px}.dropdown ul.dropdown-menu li{padding:8px 16px;cursor:pointer;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;transition:all .55s cubic-bezier(.19,1,.22,1)}.dropdown ul.dropdown-menu li.danger:hover{background-color:#ef0e5d}.dropdown ul.dropdown-menu li:hover{background-color:#027bff}.dropdown ul.dropdown-menu li a{padding:8px 16px 8px 8px;color:#fff;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;align-self:stretch}.dropdown ul.dropdown-menu li a i{margin-right:15px}@keyframes logotouch{0%{opacity:0}50%{opacity:1}to{opacity:0;left:27px}}button,.btn{position:relative;padding:10px 13px 8px 10px;font-size:14px;color:#fff;cursor:pointer;border:none;border-radius:4px;background:#027bff;box-shadow:0 2px #004a9b;font-weight:700;letter-spacing:-.5px;margin-top:-2px}button i.icon-left,.btn i.icon-left{margin-right:5px}button:hover,.btn:hover{box-shadow:0 2px 0 1px #004a9b inset;color:#fffc}button:focus,.btn:focus{background:#004a9b;color:#ffffff80;box-shadow:none}button.btn-neutral,.btn.btn-neutral{color:#aaa;background:#464646;box-shadow:0 2px #2d2d2d}button.btn-neutral:hover,.btn.btn-neutral:hover{box-shadow:0 2px 0 1px #222 inset;background:#2d2d2d}button.btn-neutral:focus,.btn.btn-neutral:focus{background:#131313}button.btn-info,.btn.btn-info{background:#027bff;box-shadow:0 2px #004a9b}button.btn-info:hover,.btn.btn-info:hover{box-shadow:0 2px 0 1px #004a9b inset}button.btn-info:focus,.btn.btn-info:focus{background:#004a9b}button.btn-naked,.btn.btn-naked{background:transparent;box-shadow:none}button.btn-naked:hover,.btn.btn-naked:hover{box-shadow:0 2px 0 1px #222 inset;background:#2d2d2d}button.btn-naked:focus,.btn.btn-naked:focus{background:#131313}button.btn-info-alt,.btn.btn-info-alt{background:#075cb7;box-shadow:0 2px #032b55}button.btn-info-alt:hover,.btn.btn-info-alt:hover{box-shadow:0 2px 0 1px #032b55 inset}button.btn-info-alt:focus,.btn.btn-info-alt:focus{background:#032b55}button.btn-success,.btn.btn-success{background:#0eef5f;box-shadow:0 2px #088f39}button.btn-success:hover,.btn.btn-success:hover{box-shadow:0 2px 0 1px #088f39 inset}button.btn-success:focus,.btn.btn-success:focus{background:#088f39}button.btn-success-alt,.btn.btn-success-alt{background:#11a948;box-shadow:0 2px #084c21}button.btn-success-alt:hover,.btn.btn-success-alt:hover{box-shadow:0 2px 0 1px #084c21 inset}button.btn-success-alt:focus,.btn.btn-success-alt:focus{background:#084c21}button.btn-danger,button.btn-error,.btn.btn-danger,.btn.btn-error{background:#ef0e5d;box-shadow:0 2px #8f0838}button.btn-danger:hover,button.btn-error:hover,.btn.btn-danger:hover,.btn.btn-error:hover{box-shadow:0 2px 0 1px #8f0838 inset}button.btn-danger:focus,button.btn-error:focus,.btn.btn-danger:focus,.btn.btn-error:focus{background:#8f0838}button.btn-danger-alt,button.btn-error-alt,.btn.btn-danger-alt,.btn.btn-error-alt{background:#c20941;box-shadow:0 2px #610420}button.btn-danger-alt:hover,button.btn-error-alt:hover,.btn.btn-danger-alt:hover,.btn.btn-error-alt:hover{box-shadow:0 2px 0 1px #610420 inset}button.btn-danger-alt:focus,button.btn-error-alt:focus,.btn.btn-danger-alt:focus,.btn.btn-error-alt:focus{background:#610420}button.btn-youtube,.btn.btn-youtube{background:#fd3c01;box-shadow:0 2px #972401}button.btn-youtube:hover,.btn.btn-youtube:hover{box-shadow:0 2px 0 1px #972401 inset}button.btn-youtube:focus,.btn.btn-youtube:focus{background:#972401}.alert{padding:20px;align-self:stretch;display:flex;flex-direction:row;justify-content:center;align-items:center;border-radius:4px}.alert-info{color:#027bff;background:#027bff33}.alert-success{color:#0eef5f;background:#0eef5f33}.alert-danger,.alert-error{color:#ef0e5d;background:#ef0e5d33}.alert i{margin-right:13px}ul.explr-tree{height:100%!important}ul.explr-tree li span{color:#aaa;font-size:17px;padding-left:1px;cursor:pointer}ul.explr-tree li span.explr-plus,ul.explr-tree li span.explr-minus{z-index:1}ul.explr-tree li span.explr-plus:hover,ul.explr-tree li span.explr-minus:hover{background:#ffffff1a;border-radius:2px}ul.explr-tree li a{color:#fff;padding-right:80px}ul.explr-tree li a:hover{color:#fff}ul.explr-tree li a.active{background:#ffffff1a;border-radius:4px;font-weight:700;text-decoration:underline;margin-left:35px;padding-left:5px;margin-right:10px}.explr-selection-actions{display:none;margin-right:10px;border-right:1px solid #222;padding-right:20px}.explr-selection .explr-selection-actions{display:flex}ul.explr-dirview{display:flex;flex-direction:row;flex-wrap:wrap}ul.explr-dirview li{display:flex;flex-direction:column;justify-content:flex-start;align-items:center;flex-shrink:0;margin:18px;min-width:90px;min-height:104px;padding-top:5px;border:1px solid transparent;border-radius:4px}ul.explr-dirview li.renaming a span{display:none}ul.explr-dirview li.renaming a form{display:block}ul.explr-dirview li.highlight-drop{border:1px dashed rgba(2,123,255,.4);background:#027bff4d}ul.explr-dirview li.highlight-clicked{border:1px dashed rgba(255,255,255,.2);background:#ffffff1a}ul.explr-dirview li a{color:#bbb;text-decoration:none;flex:1;text-align:center;font-size:12px;display:flex;flex-direction:column;justify-content:flex-start;align-items:center;max-width:84px}ul.explr-dirview li a i{font-size:64px;margin-bottom:12px;border-radius:8px}ul.explr-dirview li a input{width:100%;padding:0 3px}ul.explr-dirview li a input:focus{outline:none}ul.explr-dirview li a:hover{opacity:.8}ul.explr-dirview li a form{display:none}ul.explr-dirview li.new-folder a{color:#027bff}ul.explr-dirview li.new-folder a form{display:block}ul.explr-dirview .ui-draggable-dragging{z-index:20}ul.explr-dirview .ui-draggable-dragging a{opacity:1!important}ul.pills{background:#222;padding:6px 4px 5px;box-shadow:1px 1px .5px .5px inset #0003;border:1px solid #222;border-radius:4px;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;list-style:none;margin:0}ul.pills li.divider{margin:0 20px;width:1px;height:100%;background:#333}ul.pills li:hover a{opacity:.9}ul.pills li a{border-radius:4px;display:flex;flex-direction:row;justify-content:center;align-items:center;color:#fff;overflow:hidden;padding-right:30px;text-align:center;background:#0003;margin-right:5px;transition:all .25s cubic-bezier(.19,1,.22,1)}ul.pills li a span{display:flex;justify-content:center;align-items:center;margin-right:20px;height:42px;background:#0003;width:42px}ul.pills li.active a{color:#333;background:#fff;font-weight:700}ul.pills li.active a span{margin-right:30px}ul.pills li:hover a{color:#fff;background:#027bff}ul.pills li:last-child a{margin-right:0}.breadcrumb-container{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;align-self:stretch;border-bottom:1px solid #222;background:transparent;padding:10px}.breadcrumb-container ul.breadcrumb{background:#222;padding:6px 4px 5px;box-shadow:1px 1px .5px .5px inset #0003;border:1px solid #222;border-radius:4px;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;list-style:none;margin:0}.breadcrumb-container ul.breadcrumb li.divider{margin:0 5px}.breadcrumb-container ul.breadcrumb li.divider i{color:#aaa}.breadcrumb-container ul.breadcrumb li span,.breadcrumb-container ul.breadcrumb li a{border-radius:4px;display:flex;flex-direction:row;justify-content:center;align-items:center;color:#fff;text-align:center;padding:0 3px}.breadcrumb-container ul.breadcrumb li span i,.breadcrumb-container ul.breadcrumb li a i{margin-right:5px}.breadcrumb-container ul.breadcrumb li:hover a{color:#fff;background:#027bff}.breadcrumb-container ul.breadcrumb li:last-child a{margin-right:0}.modals{position:fixed;background:#0006;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;justify-content:center;align-items:center;z-index:10000}.modals .modals-outer{min-width:464px;display:flex;flex-direction:column;overflow:auto;padding-bottom:2px}.modals .modals-outer .modals-inner{background:#111;border-radius:10px;color:#333;padding:40px;box-shadow:0 2px #222;border:1px solid #222}.modals .modals-outer .modals-inner .modal h2{padding:0;margin:0 0 30px;font-weight:400;color:#999}.modals .modals-outer .modals-inner .modal h3{align-self:stretch;border-bottom:1px solid rgb(153,153,153);padding:15px;margin:0}.toolbar{display:flex;flex-direction:row;padding:0 25px;align-self:stretch}.toolbar h2{padding:0 25px 0 0}.toolbar .toolbar-actions{flex:1;display:flex;flex-direction:row;justify-content:flex-end;align-items:center}.panel{background:#fff;border-radius:5px;padding:25px 25px 80px;margin:10px 25px;align-self:stretch}.panel.no-border{border:none}.panel h3{color:#000}.panel-inactive{background:#ffffff1a;color:#fff;border-color:#999}.panel-inactive h3{color:#1a1a1a}.panel table{width:100%;margin-top:30px;border-collapse:collapse;text-align:left;font-weight:400}.panel th{border-bottom:1px solid rgb(255,255,255);border-collapse:collapse;padding:10px;font-weight:400}.panel-inactive th{border-color:#999}.panel td{border-collapse:collapse;padding:10px}.panel td .td-secondary{font-size:14px;opacity:.6;margin-left:3px}.panel td a.item.sort{cursor:move}.panel td a.item-name{color:#fff}.panel-inactive td a.item-name{color:#999}.panel td a.item-name:hover{text-decoration:underline}.panel td.actions a{background:#fff;color:#333;border:1px solid rgb(153,153,153);border-radius:4px;padding:2px;width:35px;display:inline-block;text-align:center;margin:0 2px}.panel td.actions a:hover{color:#0eef5f;border-color:#0eef5f}.panel td.actions a.item-edit:hover{color:#bc48ff;border-color:#bc48ff}.panel td.actions a.item-delete:hover{color:#ef0e5d;border-color:#ef0e5d}.panel td.infos{display:flex;flex-direction:row;justify-content:flex-start;align-items:flex-start}.panel td .inner{display:flex;flex-direction:row;justify-content:flex-start;align-items:center}.panel td div.badge{margin-right:5px;font-size:10px;font-weight:700}.panel a{color:#0eef5f;text-decoration:none}.panel a:hover{color:#0bbf4c}.panel.panel-menu{display:flex;flex:1;flex-direction:column;align-self:stretch;margin-right:0;border-color:#692fbd}.panel.panel-menu ul{flex:1;max-width:250px;display:flex;flex-direction:column;align-self:stretch;list-style:none;margin:0;padding:0}.panel.panel-menu ul li{margin:3px 0}.panel.panel-menu ul li a{padding:5px 15px;color:inherit;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;flex:1}.panel.panel-menu ul li:hover{color:#464646;font-weight:700}.panel.panel-menu ul li.active{color:#692fbd;background:#692fbd57;border-radius:4px;font-weight:700;border:1px solid rgb(105,47,189)}.panel.panel-menu ul li.active a{color:inherit}.pure-material-switch{z-index:0;position:relative;display:inline-block}.pure-material-switch>input{appearance:none;-moz-appearance:none;-webkit-appearance:none;z-index:-1;position:absolute;right:6px;top:-8px;display:block;margin:0;border-radius:50%;width:40px;height:40px;background-color:#00000061;outline:none;opacity:0;transform:scale(1);pointer-events:none;transition:opacity .3s .1s,transform .2s .1s}.pure-material-switch>span{display:inline-block;width:100%;cursor:pointer}.pure-material-switch>span:before{content:"";float:right;display:inline-block;margin:5px 0 5px 10px;border-radius:7px;width:36px;height:14px;background-color:#00000061;vertical-align:top;transition:background-color .2s,opacity .2s}.pure-material-switch>span:after{content:"";position:absolute;top:2px;right:16px;border-radius:50%;width:20px;height:20px;background-color:#fff;box-shadow:0 3px 1px -2px #0003,0 2px 2px #00000024,0 1px 5px #0000001f;transition:background-color .2s,transform .2s}.pure-material-switch>input:checked{right:-10px;background-color:#0eef5f}.pure-material-switch>input:checked+span:before{background-color:#0eef5f99}.pure-material-switch>input:checked+span:after{background-color:#0eef5f;transform:translate(16px)}.pure-material-switch:hover>input{opacity:.04}.pure-material-switch>input:focus{opacity:.12}.pure-material-switch:hover>input:focus{opacity:.16}.pure-material-switch>input:active{opacity:1;transform:scale(0);transition:transform 0s,opacity 0s}.pure-material-switch>input:active+span:before{background-color:#0eef5f99}.pure-material-switch>input:checked:active+span:before{background-color:#00000061}.pure-material-switch>input:disabled{opacity:0}.pure-material-switch>input:disabled+span{color:#000;opacity:.38;cursor:default}.pure-material-switch>input:disabled+span:before{background-color:#00000061}.pure-material-switch>input:checked:disabled+span:before{background-color:#0eef5f99}.form-holder{min-width:686px;display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start;align-self:stretch}.form-holder form{max-width:434px}form{display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start;align-self:stretch}form .form-group{display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start;align-self:stretch;width:100%;flex:1;margin-bottom:20px}form .form-group label{flex:1;font-size:12px;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;align-self:stretch;color:#666}form .form-group label.btn-upload{color:#fff;font-size:14px;flex:0;flex-basis:auto;margin-top:5px}form .form-group label.btn-upload input[type=file]{display:none}form .form-group label.btn-upload input[type=text]{margin-bottom:2px;padding-left:0;margin-left:10px}form .form-group label.btn-upload i{margin-left:3px;margin-right:10px}form .form-group .widget{margin-top:10px;align-self:stretch;display:flex;flex-direction:row}form .form-group .widget select,form .form-group .widget input,form .form-group .widget textarea{outline:none;padding:8px 0 5px 8px;border-radius:2px;border:1px solid rgba(255,255,255,.05);flex:1;background:#555;box-shadow:0 2px 1px #444,0 4px 2px #333 inset;color:#ddd;font-size:14px}form .form-group .widget select[disabled],form .form-group .widget input[disabled],form .form-group .widget textarea[disabled]{color:#555;background:none;box-shadow:none;border:none;border-bottom:1px solid #333;border-radius:0}form .form-group.tab-select{border-bottom:1px solid #444;display:flex;flex-direction:row;position:relative;height:48px;padding:48px 0 0;flex:0;flex-basis:auto}form .form-group.tab-select .widget{height:49px;margin-top:0;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;position:absolute;top:0;left:0;border-bottom:2px solid rgb(2,123,255);color:#027bff}form .form-group.tab-select .widget select{border:none;background:none;box-shadow:none;padding:10px 35px 10px 10px;margin:0;color:inherit;appearance:none;-moz-appearance:none;-webkit-appearance:none;text-align:left;font-weight:700;cursor:pointer;border-radius:4px 4px 0 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:200px;z-index:2}form .form-group.tab-select .widget i{margin-left:10px;margin-right:0}form .form-group.tab-select .widget i.triangle{margin-top:-4px;margin-left:0;position:absolute;right:10px}form .actions{display:flex;flex-direction:row;justify-content:flex-end;align-items:center;align-self:stretch;margin:20px 0 0}form .actions button{margin-left:25px}form .actions.actions-left{justify-content:flex-start}form .actions.actions-left button{margin-left:0}.view-content-list main .main-container .content-add-object-input{margin-bottom:6px}.view-content-edit main .main-container .bottom-content .page-content{flex:1}.view-content-edit main .main-container .bottom-content .page-content .form-holder{margin:20px 20px 20px 10px}.view-content-edit main .main-container .bottom-content .page-panel.right-panel{flex:1;align-self:stretch;display:flex;flex-direction:column;overflow:hidden;justify-content:flex-start;align-items:center;padding:20px}.view-content-edit main .main-container .bottom-content .page-panel.right-panel h3{color:#fff;padding:10px 10px 10px 0;margin-bottom:20px;font-size:16px;align-self:stretch;margin-left:-8px}.view-content-edit main .main-container .bottom-content .page-panel.right-panel h3 span{border-width:1px;border-style:solid;border-radius:4px;padding:4px 10px;margin-left:5px}.view-content-edit main .main-container .bottom-content .page-panel.right-panel h3 i{font-size:16px}.view-content-edit main .main-container .bottom-content .page-panel.right-panel .iframe-wrapper{display:flex;flex-direction:column;width:100%;position:relative;padding-top:56.25%;overflow:hidden;border-radius:4px;outline:4px solid rgba(255,255,255,.1)}.view-content-edit main .main-container .bottom-content .page-panel.right-panel .iframe-wrapper iframe{position:absolute;top:0;left:0;width:100%;height:100%;border:none} +.info{color:#027bff!important}.bg-info{background-color:#027bff!important}.border-info{border-color:#027bff!important}.success{color:#0eef5f!important}.bg-success{background-color:#0eef5f!important}.border-success{border-color:#0eef5f!important}.success-alt{color:#11a948!important}.bg-success-alt{background-color:#11a948!important}.border-success-alt{border-color:#11a948!important}.danger{color:#ef0e5d!important}.bg-danger{background-color:#ef0e5d!important}.border-danger{border-color:#ef0e5d!important}.danger-alt{color:#c20941!important}.bg-danger-alt{background-color:#c20941!important}.border-danger-alt{border-color:#c20941!important}.purple{color:#bc48ff!important}.bg-purple{background-color:#bc48ff!important}.border-purple{border-color:#bc48ff!important}.purple-alt{color:#692fbd!important}.bg-purple-alt{background-color:#692fbd!important}.border-purple-alt{border-color:#692fbd!important}.youtube{color:#fd3c01!important}.bg-youtube{background-color:#fd3c01!important}.border-youtube{border-color:#fd3c01!important}.neutral{color:#464646!important}.bg-neutral{background-color:#464646!important}.border-neutral{border-color:#464646!important}.white{color:#fff!important}.bg-white{background-color:#fff!important}.border-white{border-color:#fff!important}.black{color:#000!important}.bg-black{background-color:#000!important}.border-black{border-color:#000!important}@font-face{font-family:Sixtyfour;src:url(../../webfonts/Sixtyfour-Regular.ttf) format("truetype")}*{font-family:Roboto,Arial,"sans-serif";margin:0;padding:0;box-sizing:border-box}html{background-color:#111}body,html{height:100%;font-family:Arial,sans-serif}.container{display:flex;height:100vh}.horizontal{display:flex;flex-direction:row;justify-content:flex-start;align-items:flex-start;flex:1;align-self:stretch}main{flex:1;display:flex;flex-direction:column}main .main-container{display:flex;flex-direction:column;flex:1;overflow:hidden;align-self:stretch}main .main-container .top-content{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;align-self:stretch;padding:10px 10px 10px 20px;background:transparent;border-bottom:1px solid #222}main .main-container .top-content h1{color:#fff;font-weight:600;font-size:24px}main .main-container .top-content .top-actions{flex:1;display:flex;flex-direction:row;justify-content:flex-end;align-items:center}main .main-container .top-content .top-actions button{margin-left:10px}main .main-container .bottom-content{display:flex;flex-direction:row;align-self:stretch;justify-content:flex-start;align-items:flex-start;flex:1;overflow-y:auto;background:radial-gradient(circle at 0% 53%,rgba(239,14,93,.8) 10%,transparent 45%),radial-gradient(circle at 135% 53%,rgba(2,123,255,.8) 10%,transparent 95%),radial-gradient(circle at 50% 80%,rgba(14,239,95,.8) 40%,transparent 95%)}main .main-container .bottom-content .page-content{flex:2;overflow-y:auto;align-self:stretch;display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start;background:#000c;padding:5px}main .main-container .bottom-content .page-content .inner{flex:1;display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start;padding:0 10px 40px;background:#111;align-self:stretch}main .main-container .bottom-content .page-panel{flex:1;overflow-y:auto;align-self:stretch;background:#111;border-top:none}main .main-container .bottom-content .page-panel.left-panel{border-right:1px solid #222;border-left:none}main .main-container .bottom-content .page-panel.left-panel.explr-explorer{flex:.5;overflow-y:auto;padding:0;background:#111;box-shadow:1px 1px .5px .5px inset #0003;max-width:250px}main .main-container .bottom-content .page-panel.right-panel{border-left:1px solid #222;border-right:none}.invisible{visibility:hidden!important}.hidden{display:none!important}.tac{text-align:center!important}.tar{text-align:right!important}a{text-decoration:none}.normal{font-weight:400!important}.bold{font-weight:700!important}.col{display:flex;flex:1;flex-direction:column;align-self:stretch}main .context-bar{padding:10px;position:sticky;top:0;z-index:1000;max-height:80px;border-bottom:1px solid #222;display:flex;flex-direction:row;align-items:center}main .context-bar .context-menu{flex:1}main .context-bar .context-menu .inner{display:flex}main .context-bar .context-menu .inner ul.pills{margin:0}main .context-bar .context-divider{width:1px;height:100%;background:#222;margin-left:20px;margin-right:20px}main .context-bar .context-user{display:flex;margin-right:20px}main .context-bar .context-user .trigger{color:#fff}main .context-bar .context-user .trigger .avatar{width:32px;height:32px;border-radius:4px;background:#027bff;margin-right:10px;display:flex;flex-direction:row;justify-content:center;align-items:center;text-align:center;font-weight:700;font-size:14px;border:1px solid #444}main .context-bar .context-user .trigger i{margin-top:-5px;margin-left:10px}menu{width:300px;background:#111;overflow-y:auto;overflow-x:visible;padding:20px;z-index:2000;display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start;border-right:1px solid #222;min-width:64px}menu h1.logo{margin:40px 0 0 10px;align-self:stretch;display:flex}menu h1.logo a{text-align:center;text-shadow:0px 0 0 #fff,0px 2px 0 #444,0 0px 0 rgb(14,239,95),-0px 0 0 rgb(2,123,255),0 -0px 0 rgb(239,14,93);text-decoration:none;background:linear-gradient(90deg,#a0a0a0 0,#bebebe 46%,#dcdcdc);-webkit-background-clip:text;color:transparent;flex:1;font-family:Sixtyfour,Work Sans,Arial,"sans-serif";align-self:stretch;padding-right:3px;font-size:20px;text-transform:uppercase;transition:all .55s cubic-bezier(.19,1,.22,1);display:flex;flex-direction:row;justify-content:center;align-items:center;position:relative}menu h1.logo a img{flex-shrink:0;width:30px;margin-right:10px;position:absolute;left:5px;transition:all .55s cubic-bezier(.19,1,.22,1)}menu h1.logo a img.after{opacity:0}menu:hover h1.logo a{text-align:center;text-shadow:3px 0 0 #fff,3px 2px 0 #444,0 3px 0 rgb(14,239,95),-3px 0 0 rgb(2,123,255),0 -3px 0 rgb(239,14,93);text-decoration:none;background:linear-gradient(90deg,#a0a0a0 0,#bebebe 46%,#dcdcdc);-webkit-background-clip:text;color:transparent}menu:hover h1.logo a img.before{opacity:0}menu:hover h1.logo a img.after{animation-duration:.2s;animation-name:logotouch}menu nav{display:flex;align-self:stretch;flex:1}menu nav ul{margin:60px 0 20px;flex:1;align-self:flex-start;display:flex;flex-direction:column;list-style:none}menu nav ul li{align-self:stretch;overflow:hidden;position:relative;transition:all .55s cubic-bezier(.19,1,.22,1);margin:10px 0;border-radius:4px}menu nav ul li a{color:#ffffffe6;font-size:16px;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;align-self:stretch;flex:1;padding-top:5px;padding-bottom:5px;padding-left:10px}menu nav ul li a i{color:#fff;opacity:.2;background:transparent;display:flex;justify-content:center;align-items:center;align-self:stretch;padding:10px;width:40px;border-radius:4px;text-align:center;margin-right:20px}menu nav ul li:after{background:#fff;content:"";height:195px;left:-200px;opacity:.2;position:absolute;top:-50px;transform:rotate(35deg);transition:all .55s cubic-bezier(.19,1,.22,1);width:50px;z-index:-2;cursor:pointer}menu nav ul li.active a{color:#027bff;font-weight:700}menu nav ul li.active a i{opacity:1;background:#ffffffe6;background:#017bff}menu nav ul li:hover{background:#027bff}menu nav ul li:hover:after{z-index:2;left:120%;transition:all .55s cubic-bezier(.19,1,.22,1)}menu nav ul li:hover a{color:#fff;font-weight:700}menu nav ul li:hover a i{color:#fff;opacity:1}menu footer{background:#ffffff03;padding:20px 0;display:flex;flex-direction:row;align-self:stretch;text-align:center;justify-content:center}menu footer p{color:#444}menu footer p.version a{color:#777;font-weight:700}.dropdown{position:relative;display:flex;align-self:stretch}.dropdown.dropdown-show ul.dropdown-menu{display:flex;flex-direction:column}.dropdown .trigger{cursor:pointer;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;align-self:stretch;flex:1}.dropdown ul.dropdown-menu{position:absolute;top:100%;left:0;display:none;background-color:#222;box-shadow:0 8px 16px #0003;z-index:1000;list-style-type:none;margin:0;overflow:hidden;border-radius:4px}.dropdown ul.dropdown-menu li{padding:8px 16px;cursor:pointer;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;transition:all .55s cubic-bezier(.19,1,.22,1)}.dropdown ul.dropdown-menu li.danger:hover{background-color:#ef0e5d}.dropdown ul.dropdown-menu li:hover{background-color:#027bff}.dropdown ul.dropdown-menu li a{padding:8px 16px 8px 8px;color:#fff;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;align-self:stretch}.dropdown ul.dropdown-menu li a i{margin-right:15px}@keyframes logotouch{0%{opacity:0}50%{opacity:1}to{opacity:0;left:27px}}button,.btn{position:relative;padding:10px 13px 8px 10px;font-size:14px;color:#fff;cursor:pointer;border:none;border-radius:4px;background:#027bff;box-shadow:0 2px #004a9b;font-weight:700;letter-spacing:-.5px;margin-top:-2px}button i.icon-left,.btn i.icon-left{margin-right:5px}button:hover,.btn:hover{box-shadow:0 2px 0 1px #004a9b inset;color:#fffc}button:focus,.btn:focus{background:#004a9b;color:#ffffff80;box-shadow:none}button.btn-neutral,.btn.btn-neutral{color:#aaa;background:#464646;box-shadow:0 2px #2d2d2d}button.btn-neutral:hover,.btn.btn-neutral:hover{box-shadow:0 2px 0 1px #222 inset;background:#2d2d2d}button.btn-neutral:focus,.btn.btn-neutral:focus{background:#131313}button.btn-info,.btn.btn-info{background:#027bff;box-shadow:0 2px #004a9b}button.btn-info:hover,.btn.btn-info:hover{box-shadow:0 2px 0 1px #004a9b inset}button.btn-info:focus,.btn.btn-info:focus{background:#004a9b}button.btn-naked,.btn.btn-naked{background:transparent;box-shadow:none}button.btn-naked:hover,.btn.btn-naked:hover{box-shadow:0 2px 0 1px #222 inset;background:#2d2d2d}button.btn-naked:focus,.btn.btn-naked:focus{background:#131313}button.btn-info-alt,.btn.btn-info-alt{background:#075cb7;box-shadow:0 2px #032b55}button.btn-info-alt:hover,.btn.btn-info-alt:hover{box-shadow:0 2px 0 1px #032b55 inset}button.btn-info-alt:focus,.btn.btn-info-alt:focus{background:#032b55}button.btn-success,.btn.btn-success{background:#0eef5f;box-shadow:0 2px #088f39}button.btn-success:hover,.btn.btn-success:hover{box-shadow:0 2px 0 1px #088f39 inset}button.btn-success:focus,.btn.btn-success:focus{background:#088f39}button.btn-success-alt,.btn.btn-success-alt{background:#11a948;box-shadow:0 2px #084c21}button.btn-success-alt:hover,.btn.btn-success-alt:hover{box-shadow:0 2px 0 1px #084c21 inset}button.btn-success-alt:focus,.btn.btn-success-alt:focus{background:#084c21}button.btn-danger,button.btn-error,.btn.btn-danger,.btn.btn-error{background:#ef0e5d;box-shadow:0 2px #8f0838}button.btn-danger:hover,button.btn-error:hover,.btn.btn-danger:hover,.btn.btn-error:hover{box-shadow:0 2px 0 1px #8f0838 inset}button.btn-danger:focus,button.btn-error:focus,.btn.btn-danger:focus,.btn.btn-error:focus{background:#8f0838}button.btn-danger-alt,button.btn-error-alt,.btn.btn-danger-alt,.btn.btn-error-alt{background:#c20941;box-shadow:0 2px #610420}button.btn-danger-alt:hover,button.btn-error-alt:hover,.btn.btn-danger-alt:hover,.btn.btn-error-alt:hover{box-shadow:0 2px 0 1px #610420 inset}button.btn-danger-alt:focus,button.btn-error-alt:focus,.btn.btn-danger-alt:focus,.btn.btn-error-alt:focus{background:#610420}button.btn-youtube,.btn.btn-youtube{background:#fd3c01;box-shadow:0 2px #972401}button.btn-youtube:hover,.btn.btn-youtube:hover{box-shadow:0 2px 0 1px #972401 inset}button.btn-youtube:focus,.btn.btn-youtube:focus{background:#972401}.alert{padding:20px;align-self:stretch;display:flex;flex-direction:row;justify-content:center;align-items:center;border-radius:4px}.alert-info{color:#027bff;background:#027bff33}.alert-success{color:#0eef5f;background:#0eef5f33}.alert-danger,.alert-error{color:#ef0e5d;background:#ef0e5d33}.alert i{margin-right:13px}ul.explr-tree{height:100%!important}ul.explr-tree li span{color:#aaa;font-size:17px;padding-left:1px;cursor:pointer}ul.explr-tree li span.explr-plus,ul.explr-tree li span.explr-minus{z-index:1}ul.explr-tree li span.explr-plus:hover,ul.explr-tree li span.explr-minus:hover{background:#ffffff1a;border-radius:2px}ul.explr-tree li a{color:#fff;padding-right:80px}ul.explr-tree li a:hover{color:#fff}ul.explr-tree li a.active{background:#ffffff1a;border-radius:4px;font-weight:700;text-decoration:underline;margin-left:35px;padding-left:5px;margin-right:10px}.explr-selection-actions{display:none;margin-right:10px;border-right:1px solid #222;padding-right:20px}.explr-selection .explr-selection-actions{display:flex}ul.explr-dirview{display:flex;flex-direction:row;flex-wrap:wrap}ul.explr-dirview li{display:flex;flex-direction:column;justify-content:flex-start;align-items:center;flex-shrink:0;margin:18px;min-width:90px;min-height:104px;padding-top:5px;border:1px solid transparent;border-radius:4px}ul.explr-dirview li.renaming a span{display:none}ul.explr-dirview li.renaming a form{display:block}ul.explr-dirview li.highlight-drop{border:1px dashed rgba(2,123,255,.4);background:#027bff4d}ul.explr-dirview li.highlight-clicked{border:1px dashed rgba(255,255,255,.2);background:#ffffff1a}ul.explr-dirview li a{color:#bbb;text-decoration:none;flex:1;text-align:center;font-size:12px;display:flex;flex-direction:column;justify-content:flex-start;align-items:center;max-width:84px}ul.explr-dirview li a i{font-size:64px;margin-bottom:12px;border-radius:8px}ul.explr-dirview li a input{width:100%;padding:0 3px}ul.explr-dirview li a input:focus{outline:none}ul.explr-dirview li a:hover{opacity:.8}ul.explr-dirview li a form{display:none}ul.explr-dirview li.new-folder a{color:#027bff}ul.explr-dirview li.new-folder a form{display:block}ul.explr-dirview .ui-draggable-dragging{z-index:20}ul.explr-dirview .ui-draggable-dragging a{opacity:1!important}ul.pills{background:#222;padding:6px 4px 5px;box-shadow:1px 1px .5px .5px inset #0003;border:1px solid #222;border-radius:4px;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;list-style:none;margin:0}ul.pills li.divider{margin:0 20px;width:1px;height:100%;background:#333}ul.pills li:hover a{opacity:.9}ul.pills li a{border-radius:4px;display:flex;flex-direction:row;justify-content:center;align-items:center;color:#fff;overflow:hidden;padding-right:30px;text-align:center;background:#0003;margin-right:5px;transition:all .25s cubic-bezier(.19,1,.22,1)}ul.pills li a span{display:flex;justify-content:center;align-items:center;margin-right:20px;height:42px;background:#0003;width:42px}ul.pills li.active a{color:#333;background:#fff;font-weight:700}ul.pills li.active a span{margin-right:30px}ul.pills li:hover a{color:#fff;background:#027bff}ul.pills li:last-child a{margin-right:0}.breadcrumb-container{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;align-self:stretch;border-bottom:1px solid #222;background:transparent;padding:10px}.breadcrumb-container ul.breadcrumb{background:#222;padding:6px 4px 5px;box-shadow:1px 1px .5px .5px inset #0003;border:1px solid #222;border-radius:4px;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;list-style:none;margin:0}.breadcrumb-container ul.breadcrumb li.divider{margin:0 5px}.breadcrumb-container ul.breadcrumb li.divider i{color:#aaa}.breadcrumb-container ul.breadcrumb li span,.breadcrumb-container ul.breadcrumb li a{border-radius:4px;display:flex;flex-direction:row;justify-content:center;align-items:center;color:#fff;text-align:center;padding:0 3px}.breadcrumb-container ul.breadcrumb li span i,.breadcrumb-container ul.breadcrumb li a i{margin-right:5px}.breadcrumb-container ul.breadcrumb li:hover a{color:#fff;background:#027bff}.breadcrumb-container ul.breadcrumb li:last-child a{margin-right:0}.modals{position:fixed;background:#0006;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;justify-content:center;align-items:center;z-index:10000}.modals .modals-outer{min-width:464px;display:flex;flex-direction:column;overflow:auto;padding-bottom:2px}.modals .modals-outer .modals-inner{background:#111;border-radius:10px;color:#333;padding:40px;box-shadow:0 2px #222;border:1px solid #222}.modals .modals-outer .modals-inner .modal h2{padding:0;margin:0 0 30px;font-weight:400;color:#999}.modals .modals-outer .modals-inner .modal h3{align-self:stretch;border-bottom:1px solid rgb(153,153,153);padding:15px;margin:0}.toolbar{display:flex;flex-direction:row;padding:0 25px;align-self:stretch}.toolbar h2{padding:0 25px 0 0}.toolbar .toolbar-actions{flex:1;display:flex;flex-direction:row;justify-content:flex-end;align-items:center}.panel{background:#fff;border-radius:5px;padding:25px 25px 80px;margin:10px 25px;align-self:stretch}.panel.no-border{border:none}.panel h3{color:#000}.panel-inactive{background:#ffffff1a;color:#fff;border-color:#999}.panel-inactive h3{color:#1a1a1a}.panel table{width:100%;margin-top:30px;border-collapse:collapse;text-align:left;font-weight:400}.panel th{border-bottom:1px solid rgb(255,255,255);border-collapse:collapse;padding:10px;font-weight:400}.panel-inactive th{border-color:#999}.panel td{border-collapse:collapse;padding:10px}.panel td .td-secondary{font-size:14px;opacity:.6;margin-left:3px}.panel td a.item.sort{cursor:move}.panel td a.item-name{color:#fff}.panel-inactive td a.item-name{color:#999}.panel td a.item-name:hover{text-decoration:underline}.panel td.actions a{background:#fff;color:#333;border:1px solid rgb(153,153,153);border-radius:4px;padding:2px;width:35px;display:inline-block;text-align:center;margin:0 2px}.panel td.actions a:hover{color:#0eef5f;border-color:#0eef5f}.panel td.actions a.item-edit:hover{color:#bc48ff;border-color:#bc48ff}.panel td.actions a.item-delete:hover{color:#ef0e5d;border-color:#ef0e5d}.panel td.infos{display:flex;flex-direction:row;justify-content:flex-start;align-items:flex-start}.panel td .inner{display:flex;flex-direction:row;justify-content:flex-start;align-items:center}.panel td div.badge{margin-right:5px;font-size:10px;font-weight:700}.panel a{color:#0eef5f;text-decoration:none}.panel a:hover{color:#0bbf4c}.panel.panel-menu{display:flex;flex:1;flex-direction:column;align-self:stretch;margin-right:0;border-color:#692fbd}.panel.panel-menu ul{flex:1;max-width:250px;display:flex;flex-direction:column;align-self:stretch;list-style:none;margin:0;padding:0}.panel.panel-menu ul li{margin:3px 0}.panel.panel-menu ul li a{padding:5px 15px;color:inherit;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;flex:1}.panel.panel-menu ul li:hover{color:#464646;font-weight:700}.panel.panel-menu ul li.active{color:#692fbd;background:#692fbd57;border-radius:4px;font-weight:700;border:1px solid rgb(105,47,189)}.panel.panel-menu ul li.active a{color:inherit}.pure-material-switch{z-index:0;position:relative;display:inline-block}.pure-material-switch>input{appearance:none;-moz-appearance:none;-webkit-appearance:none;z-index:-1;position:absolute;right:6px;top:-8px;display:block;margin:0;border-radius:50%;width:40px;height:40px;background-color:#00000061;outline:none;opacity:0;transform:scale(1);pointer-events:none;transition:opacity .3s .1s,transform .2s .1s}.pure-material-switch>span{display:inline-block;width:100%;cursor:pointer}.pure-material-switch>span:before{content:"";float:right;display:inline-block;margin:5px 0 5px 10px;border-radius:7px;width:36px;height:14px;background-color:#00000061;vertical-align:top;transition:background-color .2s,opacity .2s}.pure-material-switch>span:after{content:"";position:absolute;top:2px;right:16px;border-radius:50%;width:20px;height:20px;background-color:#fff;box-shadow:0 3px 1px -2px #0003,0 2px 2px #00000024,0 1px 5px #0000001f;transition:background-color .2s,transform .2s}.pure-material-switch>input:checked{right:-10px;background-color:#0eef5f}.pure-material-switch>input:checked+span:before{background-color:#0eef5f99}.pure-material-switch>input:checked+span:after{background-color:#0eef5f;transform:translate(16px)}.pure-material-switch:hover>input{opacity:.04}.pure-material-switch>input:focus{opacity:.12}.pure-material-switch:hover>input:focus{opacity:.16}.pure-material-switch>input:active{opacity:1;transform:scale(0);transition:transform 0s,opacity 0s}.pure-material-switch>input:active+span:before{background-color:#0eef5f99}.pure-material-switch>input:checked:active+span:before{background-color:#00000061}.pure-material-switch>input:disabled{opacity:0}.pure-material-switch>input:disabled+span{color:#000;opacity:.38;cursor:default}.pure-material-switch>input:disabled+span:before{background-color:#00000061}.pure-material-switch>input:checked:disabled+span:before{background-color:#0eef5f99}.form-holder{min-width:686px;display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start;align-self:stretch}.form-holder form{max-width:434px}form{display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start;align-self:stretch}form .form-group{display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-start;align-self:stretch;width:100%;flex:1;margin-bottom:20px}form .form-group label{flex:1;font-size:12px;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;align-self:stretch;color:#666}form .form-group label.btn-upload{color:#fff;font-size:14px;flex:0;flex-basis:auto;margin-top:5px}form .form-group label.btn-upload input[type=file]{display:none}form .form-group label.btn-upload input[type=text]{margin-bottom:2px;padding-left:0;margin-left:10px}form .form-group label.btn-upload i{margin-left:3px;margin-right:10px}form .form-group .widget{margin-top:10px;align-self:stretch;display:flex;flex-direction:row}form .form-group .widget select,form .form-group .widget input,form .form-group .widget textarea{outline:none;padding:8px 0 5px 8px;border-radius:2px;border:1px solid rgba(255,255,255,.05);flex:1;background:#555;box-shadow:0 2px 1px #444,0 4px 2px #333 inset;color:#ddd;font-size:14px}form .form-group .widget select[disabled],form .form-group .widget input[disabled],form .form-group .widget textarea[disabled]{color:#555;background:none;box-shadow:none;border:none;border-bottom:1px solid #333;border-radius:0}form .form-group.tab-select{border-bottom:1px solid #444;display:flex;flex-direction:row;position:relative;height:48px;padding:48px 0 0;flex:0;flex-basis:auto}form .form-group.tab-select .widget{height:49px;margin-top:0;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;position:absolute;top:0;left:0;border-bottom:2px solid rgb(2,123,255);color:#027bff}form .form-group.tab-select .widget select{border:none;background:none;box-shadow:none;padding:10px 35px 10px 10px;margin:0;color:inherit;appearance:none;-moz-appearance:none;-webkit-appearance:none;text-align:left;font-weight:700;cursor:pointer;border-radius:4px 4px 0 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:200px;z-index:2}form .form-group.tab-select .widget i{margin-left:10px;margin-right:0}form .form-group.tab-select .widget i.triangle{margin-top:-4px;margin-left:0;position:absolute;right:10px}form .actions{display:flex;flex-direction:row;justify-content:flex-end;align-items:center;align-self:stretch;margin:20px 0 0}form .actions button{margin-left:25px}form .actions.actions-left{justify-content:flex-start}form .actions.actions-left button{margin-left:0}.view-content-list main .main-container .content-add-object-input{margin-bottom:6px}.view-content-edit main .main-container .bottom-content .page-content{flex:1}.view-content-edit main .main-container .bottom-content .page-content .form-holder{margin:20px 20px 20px 10px}.view-content-edit main .main-container .bottom-content .page-panel.right-panel{flex:1;align-self:stretch;display:flex;flex-direction:column;overflow:hidden;justify-content:flex-start;align-items:center;padding:20px}.view-content-edit main .main-container .bottom-content .page-panel.right-panel h3{color:#fff;padding:10px 10px 10px 0;margin-bottom:20px;font-size:16px;align-self:stretch;margin-left:-8px}.view-content-edit main .main-container .bottom-content .page-panel.right-panel h3 span{border-width:1px;border-style:solid;border-radius:4px;padding:4px 10px;margin-left:5px}.view-content-edit main .main-container .bottom-content .page-panel.right-panel h3 i{font-size:16px}.view-content-edit main .main-container .bottom-content .page-panel.right-panel .iframe-wrapper{display:flex;flex-direction:column;width:100%;position:relative;padding-top:56.25%;overflow:hidden;border-radius:4px;outline:4px solid rgba(255,255,255,.1)}.view-content-edit main .main-container .bottom-content .page-panel.right-panel .iframe-wrapper iframe{position:absolute;top:0;left:0;width:100%;height:100%;border:none}.view-node-player-edit main .main-container .bottom-content .page-content{flex:1}.view-node-player-edit main .main-container .bottom-content .page-content .form-holder{margin:20px 20px 20px 10px} diff --git a/data/www/css/lib/jquery-explr-1.4.css b/data/www/css/lib/jquery-explr-1.4.css index c1933fc..751192c 100755 --- a/data/www/css/lib/jquery-explr-1.4.css +++ b/data/www/css/lib/jquery-explr-1.4.css @@ -80,6 +80,17 @@ width: 16px; } +.explr-item i.fa { + position: absolute; + z-index: 1; + top: 2px; + font-size: 14px; + left: 16px; + color: rgb(187, 187, 187); + width: 18px; + text-align: center; +} + .explr-toggler { background-repeat: no-repeat; background-position: 0 0; @@ -96,7 +107,8 @@ /* Menu icons: */ -.explr-tree .icon-text > li, .explr-tree li.icon-text { background-image: url("../../img/explr/attibutes.png"); } +.explr-tree .icon-text > li, .explr-tree li.icon-text { background-image: none; } +.explr-tree .icon-file > li, .explr-tree li.icon-file { background-image: url("../../img/explr/attibutes.png"); } .explr-tree .icon-address > li, .explr-tree li.icon-address { background-image: url("../../img/explr/address.png"); } .explr-tree .icon-archives > li, .explr-tree li.icon-archives { background-image: url("../../img/explr/archives.png"); } .explr-tree .icon-badge > li, .explr-tree li.icon-badge { background-image: url("../../img/explr/bestseller.png"); } diff --git a/data/www/js/fleet/node-players-old.js b/data/www/js/fleet/node-players-old.js new file mode 100644 index 0000000..36c5884 --- /dev/null +++ b/data/www/js/fleet/node-players-old.js @@ -0,0 +1,110 @@ +jQuery(document).ready(function ($) { + const $tableActive = $('table.active-node-players'); + const $tableInactive = $('table.inactive-node-players'); + + const getId = function ($el) { + return $el.is('tr') ? $el.attr('data-level') : $el.parents('tr:eq(0)').attr('data-level'); + }; + + const updateTable = function () { + $('table').each(function () { + if ($(this).find('tbody tr.node-player-item:visible').length === 0) { + $(this).find('tr.empty-tr').removeClass('hidden'); + } else { + $(this).find('tr.empty-tr').addClass('hidden'); + } + }).tableDnDUpdate(); + updatePositions(); + }; + + const updatePositions = function (table, row) { + const positions = {}; + $('.node-player-item').each(function (index) { + positions[getId($(this))] = index; + }); + + $.ajax({ + method: 'POST', + url: '/fleet/node-player/position', + headers: {'Content-Type': 'application/json'}, + data: JSON.stringify(positions), + }); + }; + + const main = function () { + $("table").tableDnD({ + dragHandle: 'td a.node-player-sort', + onDrop: updatePositions + }); + }; + + $(document).on('change', 'select.group-picker', function () { + document.location.href = $(this).val(); + }); + + $(document).on('change', 'input[type=checkbox]', function () { + $.ajax({ + url: '/fleet/node-player/toggle', + headers: {'Content-Type': 'application/json'}, + data: JSON.stringify({id: getId($(this)), enabled: $(this).is(':checked')}), + method: 'POST', + }); + + const $tr = $(this).parents('tr:eq(0)').remove().clone(); + + if ($(this).is(':checked')) { + $tableActive.append($tr); + } else { + $tableInactive.append($tr); + } + + updateTable(); + }); + + $(document).on('change', '#node-player-add-type', function () { + const value = $(this).val(); + const inputType = $(this).find('option').filter(function (i, el) { + return $(el).val() === value; + }).data('input'); + + $('.node-player-add-object-input') + .addClass('hidden') + .prop('disabled', true) + .filter('#node-player-add-object-input-' + inputType) + .removeClass('hidden') + .prop('disabled', false) + ; + }); + + + $(document).on('click', '.node-player-add', function () { + showModal('modal-node-player-add'); + $('.modal-node-player-add input:eq(0)').focus().select(); + }); + + $(document).on('click', '.node-player-edit', function () { + const nodePlayer = JSON.parse($(this).parents('tr:eq(0)').attr('data-entity')); + showModal('modal-node-player-edit'); + $('.modal-node-player-edit input:visible:eq(0)').focus().select(); + $('#node-player-edit-name').val(nodePlayer.name); + $('#node-player-edit-group-id').val(nodePlayer.group_id); + $('#node-player-edit-host').val(nodePlayer.host); + $('#node-player-edit-id').val(nodePlayer.id); + }); + + $(document).on('click', '.node-player-delete', function () { + if (confirm(l.js_fleet_node_player_delete_confirmation)) { + const $tr = $(this).parents('tr:eq(0)'); + $tr.remove(); + updateTable(); + $.ajax({ + method: 'DELETE', + url: '/fleet/node-player/delete', + headers: {'Content-Type': 'application/json'}, + data: JSON.stringify({id: getId($(this))}), + }); + } + }); + + main(); +}); \ No newline at end of file diff --git a/data/www/js/fleet/node-players.js b/data/www/js/fleet/node-players.js index 36c5884..2796d79 100644 --- a/data/www/js/fleet/node-players.js +++ b/data/www/js/fleet/node-players.js @@ -1,110 +1,122 @@ jQuery(document).ready(function ($) { - const $tableActive = $('table.active-node-players'); - const $tableInactive = $('table.inactive-node-players'); + const initExplr = function () { + $('.explr').each(function() { + $(this).explr({ + classesPlus: 'fa fa-plus', + classesMinus: 'fa fa-minus', + onLoadFinish: function ($tree) { + $tree.removeClass('hidden'); + } + }); - const getId = function ($el) { - return $el.is('tr') ? $el.attr('data-level') : $el.parents('tr:eq(0)').attr('data-level'); + // Open complete path in explorer sidebar + explrSidebarOpenFromFolder($(this).attr('data-working-folder-id')); + }); }; - const updateTable = function () { - $('table').each(function () { - if ($(this).find('tbody tr.node-player-item:visible').length === 0) { - $(this).find('tr.empty-tr').removeClass('hidden'); - } else { - $(this).find('tr.empty-tr').addClass('hidden'); - } - }).tableDnDUpdate(); - updatePositions(); - }; - - const updatePositions = function (table, row) { - const positions = {}; - $('.node-player-item').each(function (index) { - positions[getId($(this))] = index; + const initDrags = function () { + $('.draggable').each(function() { + $(this).draggable({ + revert: "invalid", + }); }); - $.ajax({ - method: 'POST', - url: '/fleet/node-player/position', - headers: {'Content-Type': 'application/json'}, - data: JSON.stringify(positions), + $('.droppable').each(function() { + $(this).droppable({ + accept: ".draggable", + over: function (event, ui) { + $(this).addClass("highlight-drop"); + }, + out: function (event, ui) { + $(this).removeClass("highlight-drop"); + }, + drop: function (event, ui) { + $(this).removeClass("highlight-drop"); + const $form = $('#folder-move-form'); + const $moved = ui.draggable; + const $target = $(this); + $form.find('[name=is_folder]').val($moved.attr('data-folder')) + $form.find('[name=entity_id]').val($moved.attr('data-id')) + $form.find('[name=new_folder_id]').val($target.attr('data-id')) + ui.draggable.position({ + my: "center", + at: "center", + of: $(this), + using: function (pos) { + $(this).animate(pos, 50); + } + }); + $form.submit(); + } + }); }); }; const main = function () { - $("table").tableDnD({ - dragHandle: 'td a.node-player-sort', - onDrop: updatePositions - }); + initExplr(); + initDrags(); }; - $(document).on('change', 'select.group-picker', function () { - document.location.href = $(this).val(); + $(document).on('click', '.folder-add', function () { + $('.dirview .new-folder').removeClass('hidden'); + $('.page-content').animate({scrollTop: 0}, 0); + $('.dirview input').focus(); }); - $(document).on('change', 'input[type=checkbox]', function () { - $.ajax({ - url: '/fleet/node-player/toggle', - headers: {'Content-Type': 'application/json'}, - data: JSON.stringify({id: getId($(this)), enabled: $(this).is(':checked')}), - method: 'POST', - }); - - const $tr = $(this).parents('tr:eq(0)').remove().clone(); - - if ($(this).is(':checked')) { - $tableActive.append($tr); - } else { - $tableInactive.append($tr); - } - - updateTable(); - }); - - $(document).on('change', '#node-player-add-type', function () { - const value = $(this).val(); - const inputType = $(this).find('option').filter(function (i, el) { - return $(el).val() === value; - }).data('input'); - - $('.node-player-add-object-input') - .addClass('hidden') - .prop('disabled', true) - .filter('#node-player-add-object-input-' + inputType) - .removeClass('hidden') - .prop('disabled', false) - ; - }); - - $(document).on('click', '.node-player-add', function () { showModal('modal-node-player-add'); $('.modal-node-player-add input:eq(0)').focus().select(); }); - $(document).on('click', '.node-player-edit', function () { - const nodePlayer = JSON.parse($(this).parents('tr:eq(0)').attr('data-entity')); - showModal('modal-node-player-edit'); - $('.modal-node-player-edit input:visible:eq(0)').focus().select(); - $('#node-player-edit-name').val(nodePlayer.name); - $('#node-player-edit-group-id').val(nodePlayer.group_id); - $('#node-player-edit-host').val(nodePlayer.host); - $('#node-player-edit-id').val(nodePlayer.id); + $(document).on('click', '.explr-item-edit', function () { + const $item = $('.explr-dirview .highlight-clicked'); + const is_folder = $item.attr('data-folder') === '1'; + + if (is_folder) { + $item.addClass('renaming'); + $item.find('input').focus().select(); + } else { + document.location.href = $(this).attr('data-node-player-route').replace('!c!', $item.attr('data-id')); + } }); - $(document).on('click', '.node-player-delete', function () { + $(document).on('click', '.explr-item-delete', function () { + const $item = $('.explr-dirview .highlight-clicked'); + const is_folder = $item.attr('data-folder') === '1'; + let route; + + if (is_folder) { + route = $(this).attr('data-folder-route') + '?id=' + $item.attr('data-id'); + } else { + route = $(this).attr('data-node-player-route') + '?id=' + $item.attr('data-id'); + } + if (confirm(l.js_fleet_node_player_delete_confirmation)) { - const $tr = $(this).parents('tr:eq(0)'); - $tr.remove(); - updateTable(); - $.ajax({ - method: 'DELETE', - url: '/fleet/node-player/delete', - headers: {'Content-Type': 'application/json'}, - data: JSON.stringify({id: getId($(this))}), - }); + document.location.href = route; + } + }); + + $(document).on('click', '.node-player-edit', function () { + const node_player = JSON.parse($(this).parents('tr:eq(0)').attr('data-entity')); + showModal('modal-node-player-edit'); + + + + $('.modal-node-player-edit input:visible:eq(0)').focus().select(); + $('#node-player-edit-id').val(node_player.id); + }); + + $(document).on('submit', '.modal-node-player-add form', function () { + const $modal = $(this).parents('.modal:eq(0)'); + $modal.find('button[type=submit]').addClass('hidden'); + $modal.find('.btn-loading').removeClass('hidden'); + }); + + $(document).keyup(function (e) { + if (e.key === "Escape") { + $('.dirview .new-folder').addClass('hidden'); } }); main(); -}); \ No newline at end of file +}); diff --git a/data/www/js/slideshow/contents.js b/data/www/js/slideshow/contents.js index ff11b0e..e7be1ca 100644 --- a/data/www/js/slideshow/contents.js +++ b/data/www/js/slideshow/contents.js @@ -123,7 +123,7 @@ jQuery(document).ready(function ($) { $(document).on('click', '.explr-item-delete', function () { const $item = $('.explr-dirview .highlight-clicked'); const is_folder = $item.attr('data-folder') === '1'; - let route = document.location.href; + let route; if (is_folder) { route = $(this).attr('data-folder-route') + '?id=' + $item.attr('data-id'); @@ -136,16 +136,6 @@ jQuery(document).ready(function ($) { } }); - $(document).on('click', '.content-edit', function () { - const content = JSON.parse($(this).parents('tr:eq(0)').attr('data-entity')); - showModal('modal-content-edit'); - - - - $('.modal-content-edit input:visible:eq(0)').focus().select(); - $('#content-edit-id').val(content.id); - }); - $(document).on('submit', '.modal-content-add form', function () { const $modal = $(this).parents('.modal:eq(0)'); $modal.find('button[type=submit]').addClass('hidden'); diff --git a/data/www/scss/main.scss b/data/www/scss/main.scss index 41acfec..d3d93bf 100644 --- a/data/www/scss/main.scss +++ b/data/www/scss/main.scss @@ -34,5 +34,6 @@ // Import pages styles @import 'pages/content'; +@import 'pages/node_player'; //@import 'pages/settings'; //@import 'pages/sysinfo'; diff --git a/data/www/scss/pages/_node_player.scss b/data/www/scss/pages/_node_player.scss new file mode 100644 index 0000000..5043c0f --- /dev/null +++ b/data/www/scss/pages/_node_player.scss @@ -0,0 +1,22 @@ + +.view-node-player-list main .main-container { + +} + +.view-node-player-edit main .main-container { + + .bottom-content { + .page-content { + flex: 1; + + .form-holder { + margin: 20px 20px 20px 10px; + } + } + } +} + + + + + diff --git a/lang/en.json b/lang/en.json index b758793..0060d67 100644 --- a/lang/en.json +++ b/lang/en.json @@ -49,8 +49,6 @@ "slideshow_content_page_title": "Content Library", "slideshow_content_button_add": "New Content", - "slideshow_content_button_add_folder": "New Folder", - "slideshow_content_folder_not_empty_error": "Folder isn't empty, you must delete its content first", "slideshow_content_referenced_in_slide_error": "Content is referenced in a slide, remove slide first", "slideshow_content_panel_active": "Content", "slideshow_content_panel_empty": "Currently, there are no content. %link% now.", @@ -106,6 +104,7 @@ "fleet_node_player_form_label_name": "Name", "fleet_node_player_form_label_group_id": "Group", "fleet_node_player_form_label_host": "Host", + "fleet_node_player_form_label_operating_system": "OS", "fleet_node_player_form_button_cancel": "Cancel", "js_fleet_node_player_delete_confirmation": "Are you sure?", @@ -228,6 +227,8 @@ "common_validate": "Validate", "common_apply": "Apply", "common_saved": "Changes have been saved", + "common_new_folder": "New Folder", + "common_folder_not_empty_error": "Folder isn't empty, you must delete its content first", "logout": "Logout", "login_error_not_found": "Bad credentials", "login_error_bad_credentials": "Bad credentials", @@ -266,6 +267,15 @@ "enum_content_type_video_object_label": "Upload your video (MP4 only)", "enum_content_type_picture_object_label": "Upload your image", "enum_content_type_youtube_object_label": "Enter Youtube video URL", + "enum_operating_system_raspbian": "Raspbian", + "enum_operating_system_windows": "Windows", + "enum_operating_system_macos": "MacOS", + "enum_operating_system_fedora": "Fedora", + "enum_operating_system_ubuntu": "Ubuntu", + "enum_operating_system_suse": "Suse", + "enum_operating_system_redhat": "Redhat", + "enum_operating_system_centos": "CentOS", + "enum_operating_system_other": "Other", "sysinfo_rpi_model": "Raspberry Pi Model", "sysinfo_rpi_model_unknown": "Not a Raspberry Pi or model information not available", diff --git a/lang/es.json b/lang/es.json index 5b0174c..9ee0111 100644 --- a/lang/es.json +++ b/lang/es.json @@ -49,8 +49,6 @@ "slideshow_content_page_title": "Biblioteca de contenidos", "slideshow_content_button_add": "Nuevo Contenido", - "slideshow_content_button_add_folder": "Nuevo Carpeta", - "slideshow_content_folder_not_empty_error": "La carpeta no está vacía, primero debes eliminar su contenido", "slideshow_content_referenced_in_slide_error": "Se hace referencia al contenido en una diapositiva; elimine la diapositiva primero", "slideshow_content_panel_active": "Contenido", "slideshow_content_panel_empty": "Actualmente, no hay contenido. %link% ahora.", @@ -106,6 +104,7 @@ "fleet_node_player_form_label_name": "Nombre", "fleet_node_player_form_label_group_id": "Grupo", "fleet_node_player_form_label_host": "Host", + "fleet_node_player_form_label_operating_system": "OS", "fleet_node_player_form_button_cancel": "Cancelar", "js_fleet_node_player_delete_confirmation": "¿Estás seguro?", @@ -228,6 +227,8 @@ "common_validate": "Validar", "common_apply": "Aplicar", "common_saved": "Los cambios se han guardado", + "common_new_folder": "Nuevo Carpeta", + "common_folder_not_empty_error": "La carpeta no está vacía, primero debes eliminar su contenido", "logout": "Cerrar sesión", "login_error_not_found": "Credenciales incorrectas", "login_error_bad_credentials": "Credenciales incorrectas", @@ -266,6 +267,15 @@ "enum_content_type_video_object_label": "Sube tu vídeo (solo MP4)", "enum_content_type_picture_object_label": "Sube tu imagen", "enum_content_type_youtube_object_label": "Ingrese la URL del vídeo de Youtube", + "enum_operating_system_raspbian": "Raspbian", + "enum_operating_system_windows": "Windows", + "enum_operating_system_macos": "MacOS", + "enum_operating_system_fedora": "Fedora", + "enum_operating_system_ubuntu": "Ubuntu", + "enum_operating_system_suse": "Suse", + "enum_operating_system_redhat": "Redhat", + "enum_operating_system_centos": "CentOS", + "enum_operating_system_other": "Otro", "sysinfo_rpi_model": "Modelo de Raspberry Pi", "sysinfo_rpi_model_unknown": "No es una Raspberry Pi o la información del modelo no está disponible", diff --git a/lang/fr.json b/lang/fr.json index c2e5686..4b273c1 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -49,8 +49,6 @@ "slideshow_content_page_title": "Bibliothèque de contenus", "slideshow_content_button_add": "Nouveau Contenu", - "slideshow_content_button_add_folder": "Nouveau Dossier", - "slideshow_content_folder_not_empty_error": "Le dossier n'est pas vide, vous devez d'abord supprimer son contenu", "slideshow_content_referenced_in_slide_error": "Le contenu est référencé dans une slide, supprimez d'abord la slide", "slideshow_content_panel_active": "Contenus", "slideshow_content_panel_empty": "Actuellement, il n'y a aucun contenu. %link% maintenant.", @@ -106,6 +104,7 @@ "fleet_node_player_form_label_name": "Nom", "fleet_node_player_form_label_group_id": "Groupe", "fleet_node_player_form_label_host": "Hôte", + "fleet_node_player_form_label_operating_system": "OS", "fleet_node_player_form_button_cancel": "Annuler", "js_fleet_node_player_delete_confirmation": "Êtes-vous sûr ?", @@ -228,6 +227,8 @@ "common_validate": "Valider", "common_apply": "Appliquer", "common_saved": "Les modifications ont été enregistrées", + "common_new_folder": "Nouveau Dossier", + "common_folder_not_empty_error": "Le dossier n'est pas vide, vous devez d'abord supprimer son contenu", "logout": "Déconnexion", "login_error_not_found": "Identifiants invalides", "login_error_bad_credentials": "Identifiants invalides", @@ -266,6 +267,15 @@ "enum_content_type_video_object_label": "Uploadez votre vidéo (MP4 seulement)", "enum_content_type_picture_object_label": "Uploadez votre image", "enum_content_type_youtube_object_label": "Enrez l'URL de la vidéo Youtube", + "enum_operating_system_raspbian": "Raspbian", + "enum_operating_system_windows": "Windows", + "enum_operating_system_macos": "MacOS", + "enum_operating_system_fedora": "Fedora", + "enum_operating_system_ubuntu": "Ubuntu", + "enum_operating_system_suse": "Suse", + "enum_operating_system_redhat": "Redhat", + "enum_operating_system_centos": "CentOS", + "enum_operating_system_other": "Autre", "sysinfo_rpi_model": "Modèle du Raspberry Pi", "sysinfo_rpi_model_unknown": "Le modèle n'est pas un Raspberry Pi", diff --git a/lang/it.json b/lang/it.json index 8bb9358..5df97d9 100644 --- a/lang/it.json +++ b/lang/it.json @@ -49,8 +49,6 @@ "slideshow_content_page_title": "Libreria dei contenuti", "slideshow_content_button_add": "Nuovo Contenuto", - "slideshow_content_button_add_folder": "Nuovo Cartella", - "slideshow_content_folder_not_empty_error": "La cartella non è vuota, devi prima eliminarne il contenuto", "slideshow_content_referenced_in_slide_error": "Si fa riferimento al contenuto in una diapositiva, rimuovere prima la diapositiva", "slideshow_content_panel_active": "Contenuti", "slideshow_content_panel_empty": "Attualmente non ci sono contenuti. %link% adesso.", @@ -106,6 +104,7 @@ "fleet_node_player_form_label_name": "Nome", "fleet_node_player_form_label_group_id": "Group", "fleet_node_player_form_label_host": "Host", + "fleet_node_player_form_label_operating_system": "OS", "fleet_node_player_form_button_cancel": "Cancella", "js_fleet_node_player_delete_confirmation": "Sei sicuro?", @@ -228,6 +227,8 @@ "common_validate": "Convalida", "common_apply": "Applica", "common_saved": "Le modifiche sono state salvate", + "common_new_folder": "Nuovo Cartella", + "common_folder_not_empty_error": "La cartella non è vuota, devi prima eliminarne il contenuto", "logout": "Logout", "login_error_not_found": "Credenziali errate", "login_error_bad_credentials": "Credenziali errate", @@ -266,6 +267,15 @@ "enum_content_type_video_object_label": "Carica il tuo video (solo MP4)", "enum_content_type_picture_object_label": "Carica la tua immagine", "enum_content_type_youtube_object_label": "Inserisci l'URL del video Youtube", + "enum_operating_system_raspbian": "Raspbian", + "enum_operating_system_windows": "Windows", + "enum_operating_system_macos": "MacOS", + "enum_operating_system_fedora": "Fedora", + "enum_operating_system_ubuntu": "Ubuntu", + "enum_operating_system_suse": "Suse", + "enum_operating_system_redhat": "Redhat", + "enum_operating_system_centos": "CentOS", + "enum_operating_system_other": "Altro", "sysinfo_rpi_model": "Raspberry Pi Model", "sysinfo_rpi_model_unknown": "Informazioni Raspberry Pi non disponibili", diff --git a/src/controller/ContentController.py b/src/controller/ContentController.py index b899d5e..b7ff84d 100644 --- a/src/controller/ContentController.py +++ b/src/controller/ContentController.py @@ -6,7 +6,6 @@ from flask import Flask, render_template, redirect, request, url_for, send_from_ from werkzeug.utils import secure_filename from src.service.ModelStore import ModelStore from src.model.entity.Content import Content -from src.model.entity.Folder import Folder from src.model.enum.ContentType import ContentType from src.model.enum.FolderEntity import FolderEntity, FOLDER_ROOT_PATH from src.interface.ObController import ObController @@ -18,16 +17,16 @@ class ContentController(ObController): def register(self): self._app.add_url_rule('/slideshow/content', 'slideshow_content_list', self._auth(self.slideshow_content_list), methods=['GET']) - self._app.add_url_rule('/slideshow/content/add-folder', 'slideshow_content_folder_add', self._auth(self.slideshow_content_folder_add), methods=['POST']) - self._app.add_url_rule('/slideshow/content/move-folder', 'slideshow_content_folder_move', self._auth(self.slideshow_content_folder_move), methods=['POST']) - self._app.add_url_rule('/slideshow/content/rename-folder', 'slideshow_content_folder_rename', self._auth(self.slideshow_content_folder_rename), methods=['POST']) - self._app.add_url_rule('/slideshow/content/delete-folder', 'slideshow_content_folder_delete', self._auth(self.slideshow_content_folder_delete), methods=['GET']) self._app.add_url_rule('/slideshow/content/add', 'slideshow_content_add', self._auth(self.slideshow_content_add), methods=['GET', 'POST']) self._app.add_url_rule('/slideshow/content/edit/', 'slideshow_content_edit', self._auth(self.slideshow_content_edit), methods=['GET']) self._app.add_url_rule('/slideshow/content/save/', 'slideshow_content_save', self._auth(self.slideshow_content_save), methods=['POST']) self._app.add_url_rule('/slideshow/content/delete', 'slideshow_content_delete', self._auth(self.slideshow_content_delete), methods=['GET']) - self._app.add_url_rule('/slideshow/content/show/', 'slideshow_content_show', self._auth(self.slideshow_content_show), methods=['GET']) self._app.add_url_rule('/slideshow/content/cd', 'slideshow_content_cd', self._auth(self.slideshow_content_cd), methods=['GET']) + self._app.add_url_rule('/slideshow/content/add-folder', 'slideshow_content_folder_add', self._auth(self.slideshow_content_folder_add), methods=['POST']) + self._app.add_url_rule('/slideshow/content/move-folder', 'slideshow_content_folder_move', self._auth(self.slideshow_content_folder_move), methods=['POST']) + self._app.add_url_rule('/slideshow/content/rename-folder', 'slideshow_content_folder_rename', self._auth(self.slideshow_content_folder_rename), methods=['POST']) + self._app.add_url_rule('/slideshow/content/delete-folder', 'slideshow_content_folder_delete', self._auth(self.slideshow_content_folder_delete), methods=['GET']) + self._app.add_url_rule('/slideshow/content/show/', 'slideshow_content_show', self._auth(self.slideshow_content_show), methods=['GET']) def slideshow_content_list(self): working_folder_path = self._model_store.variable().get_one_by_name('last_folder_content').as_string() @@ -39,52 +38,11 @@ class ContentController(ObController): folders_tree=self._model_store.folder().get_folder_tree(FolderEntity.CONTENT), working_folder_path=working_folder_path, working_folder=working_folder, - working_folder_children=self._model_store.folder().get_children(working_folder, sort='created_at', ascending=False), + working_folder_children=self._model_store.folder().get_children(folder=working_folder, entity=FolderEntity.CONTENT, sort='created_at', ascending=False), enum_content_type=ContentType, enum_folder_entity=FolderEntity, ) - def slideshow_content_folder_add(self): - self._model_store.folder().add_folder( - entity=FolderEntity.CONTENT, - name=request.form['name'], - ) - - return redirect(url_for('slideshow_content_list')) - - def slideshow_content_folder_rename(self): - self._model_store.folder().rename_folder( - folder_id=request.form['id'], - name=request.form['name'], - ) - - return redirect(url_for('slideshow_content_list')) - - def slideshow_content_folder_move(self): - self._model_store.folder().move_to_folder( - entity_id=request.form['entity_id'], - folder_id=request.form['new_folder_id'], - entity_is_folder=True if request.form['is_folder'] == '1' else False, - ) - - return redirect(url_for('slideshow_content_list')) - - def slideshow_content_folder_delete(self): - folder = self._model_store.folder().get(request.args.get('id')) - - if not folder: - return redirect(url_for('slideshow_content_list')) - - content_counter = self._model_store.content().count_contents_for_folder(folder.id) - folder_counter = self._model_store.folder().count_subfolders_for_folder(folder.id) - - if content_counter > 0 or folder_counter: - return redirect(url_for('slideshow_content_list', folder_not_empty_error=True)) - - self._model_store.folder().delete(id=folder.id) - - return redirect(url_for('slideshow_content_list')) - def slideshow_content_add(self): working_folder_path = self._model_store.variable().get_one_by_name('last_folder_content').as_string() working_folder = self._model_store.folder().get_one_by_path(path=working_folder_path, entity=FolderEntity.CONTENT) @@ -169,6 +127,47 @@ class ContentController(ObController): return redirect(url_for('slideshow_content_list', path=path)) + def slideshow_content_folder_add(self): + self._model_store.folder().add_folder( + entity=FolderEntity.CONTENT, + name=request.form['name'], + ) + + return redirect(url_for('slideshow_content_list')) + + def slideshow_content_folder_rename(self): + self._model_store.folder().rename_folder( + folder_id=request.form['id'], + name=request.form['name'], + ) + + return redirect(url_for('slideshow_content_list')) + + def slideshow_content_folder_move(self): + self._model_store.folder().move_to_folder( + entity_id=request.form['entity_id'], + folder_id=request.form['new_folder_id'], + entity_is_folder=True if request.form['is_folder'] == '1' else False, + ) + + return redirect(url_for('slideshow_content_list')) + + def slideshow_content_folder_delete(self): + folder = self._model_store.folder().get(request.args.get('id')) + + if not folder: + return redirect(url_for('slideshow_content_list')) + + content_counter = self._model_store.content().count_contents_for_folder(folder.id) + folder_counter = self._model_store.folder().count_subfolders_for_folder(folder.id) + + if content_counter > 0 or folder_counter: + return redirect(url_for('slideshow_content_list', folder_not_empty_error=True)) + + self._model_store.folder().delete(id=folder.id) + + return redirect(url_for('slideshow_content_list')) + def slideshow_content_show(self, content_id: int = 0): content = self._model_store.content().get(content_id) diff --git a/src/controller/FleetNodePlayerController.py b/src/controller/FleetNodePlayerController.py index 55bf882..9077f33 100644 --- a/src/controller/FleetNodePlayerController.py +++ b/src/controller/FleetNodePlayerController.py @@ -1,9 +1,12 @@ import json -from flask import Flask, render_template, redirect, request, url_for, jsonify +from flask import Flask, render_template, redirect, request, url_for, jsonify, abort from src.service.ModelStore import ModelStore from src.model.entity.NodePlayer import NodePlayer from src.interface.ObController import ObController +from src.model.enum.OperatingSystem import OperatingSystem +from src.model.enum.FolderEntity import FolderEntity, FOLDER_ROOT_PATH +from src.util.utils import str_to_enum class FleetNodePlayerController(ObController): @@ -17,62 +20,151 @@ class FleetNodePlayerController(ObController): return decorated_function def register(self): - self._app.add_url_rule('/fleet/node-player/list', 'fleet_node_player_list', self.guard_fleet(self._auth(self.fleet_node_player_list)), methods=['GET']) - self._app.add_url_rule('/fleet/node-player/group/set/', 'fleet_node_player_list_group_use', self._auth(self.fleet_node_player_list), methods=['GET']) - self._app.add_url_rule('/fleet/node-player/add', 'fleet_node_player_add', self.guard_fleet(self._auth(self.fleet_node_player_add)), methods=['POST']) - self._app.add_url_rule('/fleet/node-player/edit', 'fleet_node_player_edit', self.guard_fleet(self._auth(self.fleet_node_player_edit)), methods=['POST']) - self._app.add_url_rule('/fleet/node-player/toggle', 'fleet_node_player_toggle', self.guard_fleet(self._auth(self.fleet_node_player_toggle)), methods=['POST']) - self._app.add_url_rule('/fleet/node-player/delete', 'fleet_node_player_delete', self.guard_fleet(self._auth(self.fleet_node_player_delete)), methods=['DELETE']) - self._app.add_url_rule('/fleet/node-player/position', 'fleet_node_player_position', self.guard_fleet(self._auth(self.fleet_node_player_position)), methods=['POST']) + self._app.add_url_rule('/fleet/node-player', 'fleet_node_player_list', self.guard_fleet(self._auth(self.fleet_node_player_list)), methods=['GET']) + self._app.add_url_rule('/fleet/node-player/add', 'fleet_node_player_add', self.guard_fleet(self._auth(self.fleet_node_player_add)), methods=['GET', 'POST']) + self._app.add_url_rule('/fleet/node-player/edit/', 'fleet_node_player_edit', self._auth(self.fleet_node_player_edit), methods=['GET']) + self._app.add_url_rule('/fleet/node-player/save/', 'fleet_node_player_save', self._auth(self.fleet_node_player_save), methods=['POST']) + self._app.add_url_rule('/fleet/node-player/delete', 'fleet_node_player_delete', self.guard_fleet(self._auth(self.fleet_node_player_delete)), methods=['GET']) + self._app.add_url_rule('/fleet/node-player/cd', 'fleet_node_player_cd', self._auth(self.fleet_node_player_cd), methods=['GET']) + self._app.add_url_rule('/fleet/node-player/add-folder', 'fleet_node_player_folder_add', self._auth(self.fleet_node_player_folder_add), methods=['POST']) + self._app.add_url_rule('/fleet/node-player/move-folder', 'fleet_node_player_folder_move', self._auth(self.fleet_node_player_folder_move), methods=['POST']) + self._app.add_url_rule('/fleet/node-player/rename-folder', 'fleet_node_player_folder_rename', self._auth(self.fleet_node_player_folder_rename), methods=['POST']) + self._app.add_url_rule('/fleet/node-player/delete-folder', 'fleet_node_player_folder_delete', self._auth(self.fleet_node_player_folder_delete), methods=['GET']) - def fleet_node_player_list(self, group_id: int = 0): - current_group = self._model_store.node_player_group().get(group_id) - group_id = current_group.id if current_group else None + def fleet_node_player_list(self): + working_folder_path = self._model_store.variable().get_one_by_name('last_folder_node_player').as_string() + working_folder = self._model_store.folder().get_one_by_path(path=working_folder_path, entity=FolderEntity.NODE_PLAYER) return render_template( - 'fleet/player/list.jinja.html', - current_group=current_group, - groups=self._model_store.node_player_group().get_all_labels_indexed(), - enabled_node_players=self._model_store.node_player().get_node_players(group_id=group_id, enabled=True), - disabled_node_players=self._model_store.node_player().get_node_players(group_id=group_id, enabled=False) + 'fleet/node-players/list.jinja.html', + node_players=self._model_store.node_player().get_all_indexed('folder_id', multiple=True), + folders_tree=self._model_store.folder().get_folder_tree(FolderEntity.NODE_PLAYER), + working_folder_path=working_folder_path, + working_folder=working_folder, + working_folder_children=self._model_store.folder().get_children(folder=working_folder, entity=FolderEntity.NODE_PLAYER, sort='created_at', ascending=False), + enum_operating_system=OperatingSystem, + enum_folder_entity=FolderEntity, ) def fleet_node_player_add(self): - node_player = NodePlayer( + working_folder_path = self._model_store.variable().get_one_by_name('last_folder_node_player').as_string() + working_folder = self._model_store.folder().get_one_by_path(path=working_folder_path, entity=FolderEntity.NODE_PLAYER) + + self._model_store.node_player().add_form( + NodePlayer( + name=request.form['name'], + host=request.form['host'], + operating_system=str_to_enum(request.form['operating_system'], OperatingSystem), + folder_id=working_folder.id if working_folder else None, + ) + ) + + return redirect(url_for('fleet_node_player_list')) + + def fleet_node_player_edit(self, node_player_id: int = 0): + node_player = self._model_store.node_player().get(node_player_id) + + if not node_player: + return abort(404) + + working_folder_path = self._model_store.variable().get_one_by_name('last_folder_node_player').as_string() + working_folder = self._model_store.folder().get_one_by_path(path=working_folder_path, entity=FolderEntity.NODE_PLAYER) + + return render_template( + 'fleet/node-players/edit.jinja.html', + node_player=node_player, + working_folder_path=working_folder_path, + working_folder=working_folder, + enum_operating_system=OperatingSystem, + ) + + def fleet_node_player_save(self, node_player_id: int = 0): + node_player = self._model_store.node_player().get(node_player_id) + + if not node_player: + return redirect(url_for('fleet_node_player_list')) + + self._model_store.node_player().update_form( + id=node_player.id, name=request.form['name'], + operating_system=str_to_enum(request.form['operating_system'], OperatingSystem), host=request.form['host'], - group_id=request.form['group_id'] if 'group_id' in request.form and request.form['group_id'] else None, ) - self._model_store.node_player().add_form(node_player) + self._post_update() - if node_player.group_id: - return redirect(url_for('fleet_node_player_list_group_use', group_id=node_player.group_id)) - - return redirect(url_for('fleet_node_player_list')) - - def fleet_node_player_edit(self): - node_player = self._model_store.node_player().update_form( - request.form['id'], - request.form['name'], - request.form['host'], - request.form['group_id'] - ) - - if node_player.group_id: - return redirect(url_for('fleet_node_player_list_group_use', group_id=node_player.group_id)) - - return redirect(url_for('fleet_node_player_list')) - - def fleet_node_player_toggle(self): - data = request.get_json() - self._model_store.node_player().update_enabled(data.get('id'), data.get('enabled')) - return jsonify({'status': 'ok'}) + return redirect(url_for('fleet_node_player_edit', node_player_id=node_player_id, saved=1)) def fleet_node_player_delete(self): - data = request.get_json() - self._model_store.node_player().delete(data.get('id')) - return jsonify({'status': 'ok'}) + node_player = self._model_store.node_player().get(request.args.get('id')) - def fleet_node_player_position(self): - data = request.get_json() - self._model_store.node_player().update_positions(data) - return jsonify({'status': 'ok'}) + if not node_player: + return redirect(url_for('fleet_node_player_list')) + + self._model_store.node_player().delete(node_player.id) + self._post_update() + return redirect(url_for('fleet_node_player_list')) + + def fleet_node_player_cd(self): + path = request.args.get('path') + + if path == FOLDER_ROOT_PATH: + self._model_store.variable().update_by_name("last_folder_node_player", FOLDER_ROOT_PATH) + return redirect(url_for('fleet_node_player_list', path=FOLDER_ROOT_PATH)) + + if not path: + return abort(404) + + cd_folder = self._model_store.folder().get_one_by_path( + path=path, + entity=FolderEntity.NODE_PLAYER + ) + + if not cd_folder: + return abort(404) + + self._model_store.variable().update_by_name("last_folder_node_player", path) + + return redirect(url_for('fleet_node_player_list', path=path)) + + def fleet_node_player_folder_add(self): + self._model_store.folder().add_folder( + entity=FolderEntity.NODE_PLAYER, + name=request.form['name'], + ) + + return redirect(url_for('fleet_node_player_list')) + + def fleet_node_player_folder_rename(self): + self._model_store.folder().rename_folder( + folder_id=request.form['id'], + name=request.form['name'], + ) + + return redirect(url_for('fleet_node_player_list')) + + def fleet_node_player_folder_move(self): + self._model_store.folder().move_to_folder( + entity_id=request.form['entity_id'], + folder_id=request.form['new_folder_id'], + entity_is_folder=True if request.form['is_folder'] == '1' else False, + ) + + return redirect(url_for('fleet_node_player_list')) + + def fleet_node_player_folder_delete(self): + folder = self._model_store.folder().get(request.args.get('id')) + + if not folder: + return redirect(url_for('fleet_node_player_list')) + + node_player_counter = self._model_store.node_player().count_node_players_for_folder(folder.id) + folder_counter = self._model_store.folder().count_subfolders_for_folder(folder.id) + + if node_player_counter > 0 or folder_counter: + return redirect(url_for('fleet_node_player_list', folder_not_empty_error=True)) + + self._model_store.folder().delete(id=folder.id) + + return redirect(url_for('fleet_node_player_list')) + + def _post_update(self): + pass diff --git a/src/manager/ContentManager.py b/src/manager/ContentManager.py index 5b4206e..4552c7a 100644 --- a/src/manager/ContentManager.py +++ b/src/manager/ContentManager.py @@ -63,7 +63,7 @@ class ContentManager(ModelManager): return self.hydrate_object(object) def get_all(self, sort: Optional[str] = 'created_at', ascending=False) -> List[Content]: - return self.hydrate_list(self._db.get_all(self.TABLE_NAME, sort=sort, ascending=ascending)) + return self.hydrate_list(self._db.get_all(table_name=self.TABLE_NAME, sort=sort, ascending=ascending)) def get_all_indexed(self, attribute: str = 'id', multiple=False) -> Dict[str, Content]: index = {} diff --git a/src/manager/FolderManager.py b/src/manager/FolderManager.py index 18e7195..ce88eba 100644 --- a/src/manager/FolderManager.py +++ b/src/manager/FolderManager.py @@ -4,6 +4,7 @@ from src.model.entity.Folder import Folder from src.model.enum.FolderEntity import FolderEntity, FOLDER_ROOT_PATH, FOLDER_ROOT_NAME from src.manager.DatabaseManager import DatabaseManager from src.manager.ContentManager import ContentManager +from src.manager.NodePlayerManager import NodePlayerManager from src.manager.LangManager import LangManager from src.manager.UserManager import UserManager from src.manager.VariableManager import VariableManager @@ -47,11 +48,16 @@ class FolderManager(ModelManager): def get_by_entity(self, entity: FolderEntity) -> List[Folder]: return self.get_by("entity = '{}'".format(entity.value)) - def get_children(self, folder: Optional[Folder], sort: Optional[str] = None, ascending=True) -> List[Folder]: - if folder: - return self.get_by("parent_id = {}".format(folder.id), sort, ascending) + def get_children(self, folder: Optional[Folder], entity: Optional[FolderEntity] = None, sort: Optional[str] = None, ascending=True) -> List[Folder]: + query = " 1=1 " - return self.get_by("parent_id is null", sort, ascending) + if entity: + query = "{} {}".format(query, "AND entity = '{}'".format(entity.value)) + + if folder: + return self.get_by("parent_id = {} AND {}".format(folder.id, query), sort, ascending) + + return self.get_by("parent_id is null AND {}".format(query), sort, ascending) def get_one_by_path(self, path: str, entity: FolderEntity) -> Folder: parts = path[1:].split('/') @@ -138,9 +144,16 @@ class FolderManager(ModelManager): params=(folder_id if folder else None, folder.depth + 1 if folder else 1, entity_id) ) + table = None + if folder.entity == FolderEntity.CONTENT: + table = ContentManager.TABLE_NAME + elif folder.entity == FolderEntity.NODE_PLAYER: + table = NodePlayerManager.TABLE_NAME + + if table: return self._db.execute_write_query( - query="UPDATE {} set folder_id = ? WHERE id = ?".format(ContentManager.TABLE_NAME), + query="UPDATE {} set folder_id = ? WHERE id = ?".format(table), params=(folder_id, entity_id) ) @@ -148,6 +161,8 @@ class FolderManager(ModelManager): var_name = None if entity == FolderEntity.CONTENT: var_name = "last_folder_content" + elif entity == FolderEntity.NODE_PLAYER: + var_name = "last_folder_node_player" if not var_name: raise Error("No variable for entity {}".format(entity.value)) @@ -164,7 +179,7 @@ class FolderManager(ModelManager): def add_folder(self, entity: FolderEntity, name: str) -> Folder: working_folder_path = self.get_working_folder(entity) - working_folder = self.get_one_by_path(path=working_folder_path, entity=FolderEntity.CONTENT) + working_folder = self.get_one_by_path(path=working_folder_path, entity=entity) folder_path = "{}/{}".format(working_folder_path, name) parts = folder_path[1:].split('/') depth = len(parts) - 1 diff --git a/src/manager/NodePlayerManager.py b/src/manager/NodePlayerManager.py index 1102f93..2bc6a9e 100644 --- a/src/manager/NodePlayerManager.py +++ b/src/manager/NodePlayerManager.py @@ -1,6 +1,7 @@ from typing import Dict, Optional, List, Tuple, Union from src.model.entity.NodePlayer import NodePlayer +from src.model.enum.OperatingSystem import OperatingSystem from src.manager.DatabaseManager import DatabaseManager from src.manager.LangManager import LangManager from src.manager.UserManager import UserManager @@ -13,10 +14,10 @@ class NodePlayerManager(ModelManager): TABLE_NAME = "fleet_player" TABLE_MODEL = [ "name CHAR(255)", - "enabled INTEGER DEFAULT 0", - "group_id INTEGER", - "position INTEGER", "host CHAR(255)", + "operating_system CHAR(100)", + "folder_id INTEGER", + "group_id INTEGER", "created_by CHAR(255)", "updated_by CHAR(255)", "created_at INTEGER", @@ -31,6 +32,11 @@ class NodePlayerManager(ModelManager): if id: raw_node_player['id'] = id + [raw_node_player, user_tracker_edits] = self.user_manager.initialize_user_trackers(raw_node_player) + + if len(user_tracker_edits) > 0: + self._db.update_by_id(self.TABLE_NAME, raw_node_player['id'], user_tracker_edits) + return NodePlayer(**raw_node_player) def hydrate_list(self, raw_node_players: list) -> List[NodePlayer]: @@ -51,17 +57,22 @@ class NodePlayerManager(ModelManager): return self.hydrate_object(object) - def get_all(self, sort: bool = False) -> List[NodePlayer]: - return self.hydrate_list(self._db.get_all(self.TABLE_NAME, "position" if sort else None)) + def get_all(self, sort: Optional[str] = 'created_at', ascending=False) -> List[NodePlayer]: + return self.hydrate_list(self._db.get_all(table_name=self.TABLE_NAME, sort=sort, ascending=ascending)) - def get_node_players(self, group_id: Optional[int] = None, enabled: bool = True) -> List[NodePlayer]: - query = "enabled = {}".format("1" if enabled else "0") - if group_id: - query = "{} {}".format(query, "AND group_id = {}".format(group_id)) - else: - query = "{} {}".format(query, "AND group_id is NULL") + def get_all_indexed(self, attribute: str = 'id', multiple=False) -> Dict[str, NodePlayer]: + index = {} - return self.get_by(query=query, sort="position") + for item in self.get_node_players(): + id = getattr(item, attribute) + if multiple: + if id not in index: + index[id] = [] + index[id].append(item) + else: + index[id] = item + + return index def forget_for_user(self, user_id: int): node_players = self.get_by("created_by = '{}' or updated_by = '{}'".format(user_id, user_id)) @@ -70,6 +81,17 @@ class NodePlayerManager(ModelManager): for node_player_id, edits in edits_node_players.items(): self._db.update_by_id(self.TABLE_NAME, node_player_id, edits) + def get_node_players(self, group_id: Optional[int] = None, folder_id: Optional[id] = None) -> List[NodePlayer]: + query = " 1=1 " + + if group_id: + query = "{} {}".format(query, "AND group_id = {}".format(group_id)) + + if folder_id: + query = "{} {}".format(query, "AND folder_id = {}".format(folder_id)) + + return self.get_by(query=query) + def pre_add(self, node_player: Dict) -> Dict: self.user_manager.track_user_on_create(node_player) self.user_manager.track_user_on_update(node_player) @@ -91,21 +113,7 @@ class NodePlayerManager(ModelManager): def post_delete(self, node_player_id: str) -> str: return node_player_id - def get_enabled_node_players(self) -> List[NodePlayer]: - return self.get_by(query="enabled = 1", sort="position") - - def get_disabled_node_players(self) -> List[NodePlayer]: - return self.get_by(query="enabled = 0", sort="position") - - def update_enabled(self, id: int, enabled: bool) -> None: - self._db.update_by_id(self.TABLE_NAME, id, self.pre_update({"enabled": enabled, "position": 999})) - self.post_update(id) - - def update_positions(self, positions: list) -> None: - for node_player_id, node_player_position in positions.items(): - self._db.update_by_id(self.TABLE_NAME, node_player_id, {"position": node_player_position}) - - def update_form(self, id: int, name: str, host: str, group_id: Optional[int]) -> NodePlayer: + def update_form(self, id: int, name: str, host: str, operating_system: Optional[OperatingSystem] = None, group_id: Optional[int] = None) -> NodePlayer: node_player = self.get(id) if not node_player: @@ -114,6 +122,7 @@ class NodePlayerManager(ModelManager): form = { "name": name, "host": host, + "operating_system": operating_system, "group_id": group_id if group_id else None } @@ -141,3 +150,6 @@ class NodePlayerManager(ModelManager): def count_node_players_for_group(self, id: int) -> int: return len(self.get_node_players(group_id=id)) + + def count_node_players_for_folder(self, folder_id: int) -> int: + return len(self.get_node_players(folder_id=folder_id)) diff --git a/src/manager/VariableManager.py b/src/manager/VariableManager.py index 23b37b7..f50300a 100644 --- a/src/manager/VariableManager.py +++ b/src/manager/VariableManager.py @@ -133,6 +133,7 @@ class VariableManager: # Not editable (System information) {"name": "last_folder_content", "value": FOLDER_ROOT_PATH, "type": VariableType.STRING, "editable": False, "description": self.t('settings_variable_desc_ro_last_folder_content')}, + {"name": "last_folder_node_player", "value": FOLDER_ROOT_PATH, "type": VariableType.STRING, "editable": False, "description": self.t('settings_variable_desc_ro_last_folder_node_player')}, {"name": "last_restart", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": self.t('settings_variable_desc_ro_editable')}, {"name": "last_slide_update", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": self.t('settings_variable_desc_ro_last_slide_update')}, {"name": "refresh_player_request", "value": time.time(), "type": VariableType.TIMESTAMP, "editable": False, "description": self.t('settings_variable_desc_ro_refresh_player_request')}, diff --git a/src/model/entity/NodePlayer.py b/src/model/entity/NodePlayer.py index 4b62a87..90a8ead 100644 --- a/src/model/entity/NodePlayer.py +++ b/src/model/entity/NodePlayer.py @@ -2,17 +2,19 @@ import json import time from typing import Optional, Union +from src.model.enum.OperatingSystem import OperatingSystem +from src.util.utils import str_to_enum class NodePlayer: - def __init__(self, host: str = '', enabled: bool = False, name: str = 'Untitled', position: int = 999, id: Optional[int] = None, group_id: Optional[int] = None, created_by: Optional[str] = None, updated_by: Optional[str] = None, created_at: Optional[int] = None, updated_at: Optional[int] = None): + def __init__(self, host: str = '', name: str = 'Untitled', operating_system: Optional[OperatingSystem] = None, id: Optional[int] = None, group_id: Optional[int] = None, created_by: Optional[str] = None, updated_by: Optional[str] = None, created_at: Optional[int] = None, updated_at: Optional[int] = None, folder_id: Optional[int] = None): self._id = id if id else None self._group_id = group_id self._host = host - self._enabled = enabled + self._operating_system = str_to_enum(operating_system, OperatingSystem) if isinstance(operating_system, str) else operating_system self._name = name - self._position = position + self._folder_id = folder_id self._created_by = created_by if created_by else None self._updated_by = updated_by if updated_by else None self._created_at = int(created_at if created_at else time.time()) @@ -38,14 +40,6 @@ class NodePlayer: def host(self, value: str): self._host = value - @property - def enabled(self) -> bool: - return bool(self._enabled) - - @enabled.setter - def enabled(self, value: bool): - self._enabled = bool(value) - @property def name(self) -> str: return self._name @@ -54,14 +48,6 @@ class NodePlayer: def name(self, value: str): self._name = value - @property - def position(self) -> int: - return self._position - - @position.setter - def position(self, value: int): - self._position = value - @property def created_by(self) -> str: return self._created_by @@ -78,6 +64,14 @@ class NodePlayer: def updated_by(self, value: str): self._updated_by = value + @property + def folder_id(self) -> Optional[int]: + return self._folder_id + + @folder_id.setter + def folder_id(self, value: Optional[int]): + self._folder_id = value + @property def created_at(self) -> int: return self._created_at @@ -94,18 +88,26 @@ class NodePlayer: def updated_at(self, value: int): self._updated_at = value + @property + def operating_system(self) -> Optional[OperatingSystem]: + return self._operating_system + + @operating_system.setter + def operating_system(self, value: Optional[OperatingSystem]): + self._operating_system = value + def __str__(self) -> str: return f"NodePlayer(" \ f"id='{self.id}',\n" \ f"group_id='{self.group_id}',\n" \ f"name='{self.name}',\n" \ - f"enabled='{self.enabled}',\n" \ - f"position='{self.position}',\n" \ + f"operating_system='{self.operating_system}',\n" \ f"host='{self.host}',\n" \ f"created_by='{self.created_by}',\n" \ f"updated_by='{self.updated_by}',\n" \ f"created_at='{self.created_at}',\n" \ f"updated_at='{self.updated_at}',\n" \ + f"folder_id='{self.folder_id}',\n" \ f")" def to_json(self, edits: dict = {}) -> str: @@ -121,11 +123,11 @@ class NodePlayer: "id": self.id, "group_id": self.group_id, "name": self.name, - "enabled": self.enabled, - "position": self.position, + "operating_system": self.operating_system.value, "host": self.host, "created_by": self.created_by, "updated_by": self.updated_by, "created_at": self.created_at, "updated_at": self.updated_at, + "folder_id": self.folder_id, } diff --git a/src/model/enum/ContentType.py b/src/model/enum/ContentType.py index 1d1e7e1..b4db77e 100644 --- a/src/model/enum/ContentType.py +++ b/src/model/enum/ContentType.py @@ -33,7 +33,7 @@ class ContentType(Enum): return ContentInputType.TEXT @staticmethod - def get_fa_icon(value: Enum) -> ContentInputType: + def get_fa_icon(value: Enum) -> str: if value == ContentType.PICTURE: return 'fa-regular fa-image' elif value == ContentType.VIDEO: @@ -46,7 +46,7 @@ class ContentType(Enum): return 'fa-file' @staticmethod - def get_color_icon(value: Enum) -> ContentInputType: + def get_color_icon(value: Enum) -> str: if value == ContentType.PICTURE: return 'info' elif value == ContentType.VIDEO: diff --git a/src/model/enum/OperatingSystem.py b/src/model/enum/OperatingSystem.py new file mode 100644 index 0000000..0f130cd --- /dev/null +++ b/src/model/enum/OperatingSystem.py @@ -0,0 +1,37 @@ +from enum import Enum + + +class OperatingSystem(Enum): + + RASPBIAN = 'raspbian' + WINDOWS = 'windows' + MACOS = 'macos' + FEDORA = 'fedora' + UBUNTU = 'ubuntu' + SUSE = 'suse' + REDHAT = 'redhat' + CENTOS = 'centos' + OTHER = 'other' + + @staticmethod + def get_fa_icon(value: Enum) -> str: + if value == OperatingSystem.RASPBIAN: + return 'fa-brands fa-raspberry-pi' + elif value == OperatingSystem.WINDOWS: + return 'fa-brands fa-windows' + elif value == OperatingSystem.MACOS: + return 'fa-brands fa-apple' + elif value == OperatingSystem.FEDORA: + return 'fa-brands fa-fedora' + elif value == OperatingSystem.UBUNTU: + return 'fa-brands fa-ubuntu' + elif value == OperatingSystem.SUSE: + return 'fa-brands fa-suse' + elif value == OperatingSystem.REDHAT: + return 'fa-brands fa-redhat' + elif value == OperatingSystem.CENTOS: + return 'fa-brands fa-centos' + elif value == OperatingSystem.OTHER: + return 'fa-server' + + return 'fa-server' diff --git a/views/fleet/node-players/edit.jinja.html b/views/fleet/node-players/edit.jinja.html new file mode 100644 index 0000000..01e0f56 --- /dev/null +++ b/views/fleet/node-players/edit.jinja.html @@ -0,0 +1,105 @@ +{% set active_pill_route='fleet_node_player_list' %} +{% extends 'base.jinja.html' %} + +{% block page_title %} + {{ l.fleet_node_player_page_title }} +{% endblock %} + +{% block add_css %} + + + {{ HOOK(H_SLIDESHOW_CONTENT_CSS) }} +{% endblock %} + +{% block add_js %} + + + + {{ HOOK(H_SLIDESHOW_CONTENT_JAVASCRIPT) }} +{% endblock %} + +{% block body_class %}view-node-player-edit edit-page{% endblock %} + +{% block page %} +
+

+ {{ l.fleet_node_player_form_edit_title }} +

+
+ + {% if request.args.get('saved') %} +
+ + {{ l.common_saved }} +
+ {% endif %} + +
+
+
+ + +
+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+
+
+
+
+
+
+ + +{% endblock %} diff --git a/views/fleet/node-players/list.jinja.html b/views/fleet/node-players/list.jinja.html new file mode 100644 index 0000000..bf70da0 --- /dev/null +++ b/views/fleet/node-players/list.jinja.html @@ -0,0 +1,195 @@ +{% extends 'base.jinja.html' %} + +{% block page_title %} + {{ l.fleet_node_player_page_title }} +{% endblock %} + +{% block add_css %} + + {{ HOOK(H_SLIDESHOW_CONTENT_CSS) }} +{% endblock %} + +{% block add_js %} + + + + + {{ HOOK(H_SLIDESHOW_CONTENT_JAVASCRIPT) }} +{% endblock %} + +{% block body_class %}view-node-player-list{% endblock %} + +{% block page %} +
+

+ {{ l.fleet_node_player_page_title }} +

+ +
+ {{ HOOK(H_SLIDESHOW_CONTENT_TOOLBAR_ACTIONS_START) }} + +
+ + +
+ + + + {{ HOOK(H_SLIDESHOW_CONTENT_TOOLBAR_ACTIONS_END) }} +
+
+ + {% if request.args.get('folder_not_empty_error') %} +
+ + {{ l.common_folder_not_empty_error }} +
+ {% endif %} + + {% if request.args.get('referenced_in_node_player_group_error') %} +
+ + {{ l.fleet_node_player_referenced_in_node_player_group_error }} +
+ {% endif %} + +
+
+ + {% macro render_folder(folder) %} + {% set node_player_children = node_players[folder.id]|default([]) %} + {% set has_children = folder.children or node_player_children %} + +
  • + + {{ folder.name }} + + + {% if has_children %} +
      + {% for child in folder.children %} + {{ render_folder(child) }} + {% endfor %} + {% for node_player in node_player_children %} + {% set icon = enum_operating_system.get_fa_icon(node_player.operating_system) %} +
    • + + + {{ node_player.name }} + +
    • + {% endfor %} +
    + {% endif %} +
  • + {% endmacro %} + + +
    + + + +
    +
    + + +
    + + +
    +
    + + +{% endblock %} diff --git a/views/fleet/node-players/modal/add.jinja.html b/views/fleet/node-players/modal/add.jinja.html new file mode 100644 index 0000000..39821fc --- /dev/null +++ b/views/fleet/node-players/modal/add.jinja.html @@ -0,0 +1,47 @@ + \ No newline at end of file diff --git a/views/fleet/player/component/table.jinja.html b/views/fleet/player/component/table.jinja.html deleted file mode 100644 index 7458e83..0000000 --- a/views/fleet/player/component/table.jinja.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - {% if AUTH_ENABLED %} - - {% endif %} - - - - - - - - - - {% for node_player in node_players %} - - - {% if AUTH_ENABLED %} - - {% endif %} - - - - - {% endfor %} - -
    {{ l.fleet_node_player_panel_th_name }} - - {{ l.fleet_node_player_panel_th_host }}{{ l.fleet_node_player_panel_th_enabled }}{{ l.fleet_node_player_panel_th_activity }}
    - {{ l.fleet_node_player_panel_empty|replace( - '%link%', - (''~l.fleet_node_player_button_add~'')|safe - ) }} -
    -
    - - - - -
    {{ node_player.id }}
    - - - {{ node_player.name }} -
    -
    - {% set creator = track_created(node_player) %} - {% if creator.username %} - - {{ creator.username }} - - {% endif %} - - {{ node_player.host }} - - - - - - - - - -
    \ No newline at end of file diff --git a/views/fleet/player/list.jinja.html b/views/fleet/player/list.jinja.html deleted file mode 100644 index 44dd3c3..0000000 --- a/views/fleet/player/list.jinja.html +++ /dev/null @@ -1,73 +0,0 @@ -{% extends 'base.jinja.html' %} - -{% block page_title %} - {{ l.fleet_node_player_page_title }} -{% endblock %} - -{% block add_css %} - {{ HOOK(H_FLEET_NODE_PLAYER_CSS) }} -{% endblock %} - -{% block add_js %} - - - {{ HOOK(H_FLEET_NODE_PLAYER_JAVASCRIPT) }} -{% endblock %} - -{% block page %} -
    -

    {{ l.fleet_node_player_page_title }}

    - -
    - {{ HOOK(H_FLEET_NODE_PLAYER_TOOLBAR_ACTIONS_START) }} - - - - - {{ HOOK(H_FLEET_NODE_PLAYER_TOOLBAR_ACTIONS_END) }} -
    -
    - - -
    -
    -

    {{ l.fleet_node_player_panel_active }}

    - - {% with tclass='active', node_players=enabled_node_players %} - {% include 'fleet/player/component/table.jinja.html' %} - {% endwith %} -
    -
    -
    -
    -

    {{ l.fleet_node_player_panel_inactive }}

    - - {% with tclass='inactive', node_players=disabled_node_players %} - {% include 'fleet/player/component/table.jinja.html' %} - {% endwith %} -
    -
    - - -{% endblock %} diff --git a/views/fleet/player/modal/add.jinja.html b/views/fleet/player/modal/add.jinja.html deleted file mode 100644 index 3c384f5..0000000 --- a/views/fleet/player/modal/add.jinja.html +++ /dev/null @@ -1,42 +0,0 @@ - \ No newline at end of file diff --git a/views/fleet/player/modal/edit.jinja.html b/views/fleet/player/modal/edit.jinja.html deleted file mode 100644 index ed89ecc..0000000 --- a/views/fleet/player/modal/edit.jinja.html +++ /dev/null @@ -1,44 +0,0 @@ - \ No newline at end of file diff --git a/views/slideshow/contents/list.jinja.html b/views/slideshow/contents/list.jinja.html index 28277d6..895bb77 100644 --- a/views/slideshow/contents/list.jinja.html +++ b/views/slideshow/contents/list.jinja.html @@ -41,9 +41,9 @@ {{ l.slideshow_content_button_add }} - {{ HOOK(H_SLIDESHOW_CONTENT_TOOLBAR_ACTIONS_END) }} @@ -52,7 +52,7 @@ {% if request.args.get('folder_not_empty_error') %}
    - {{ l.slideshow_content_folder_not_empty_error }} + {{ l.common_folder_not_empty_error }}
    {% endif %} @@ -80,19 +80,11 @@ {% for child in folder.children %} {{ render_folder(child) }} {% endfor %} - {% for content in content_children %} - {% set icon = 'icon-folder' %} - {% if content.type.value == 'picture' %} - {% set icon = 'icon-landscape' %} - {% elif content.type.value == 'video' %} - {% set icon = 'icon-video' %} - {% elif content.type.value == 'url' %} - {% set icon = 'icon-chain' %} - {% elif content.type.value == 'youtube' %} - {% set icon = 'icon-youtube' %} - {% endif %} - -
  • + {% for content in content_children %} + {% set icon = enum_content_type.get_fa_icon(content.type) %} + {% set color = enum_content_type.get_color_icon(content.type) %} +
  • + {{ content.name }} @@ -178,11 +170,12 @@ {% for content in contents[working_folder.id|default(None)]|default([]) %} - {% set icon = enum_content_type.get_fa_icon(content.type) ~ ' ' ~ enum_content_type.get_color_icon(content.type) %} + {% set icon = enum_content_type.get_fa_icon(content.type) %} + {% set color = enum_content_type.get_color_icon(content.type) %}
  • - + {{ truncate(content.name, 25, '...') }}
  • diff --git a/views/slideshow/slides/component/table.jinja.html b/views/slideshow/slides/component/table.jinja.html index 43135e7..313506a 100644 --- a/views/slideshow/slides/component/table.jinja.html +++ b/views/slideshow/slides/component/table.jinja.html @@ -49,7 +49,7 @@ {% if slide.cron_schedule %} - {% set cron_desc = cron_descriptor(slide.cron_schedule) %} + {% set cron_desc = cron_descriptor(slide.cron_schedule) %} {% if cron_desc %} {% if is_valid_cron_date_time(slide.cron_schedule) %} {% if slide.is_notification %} @@ -69,7 +69,7 @@ {% if slide.cron_schedule_end %} - {% set cron_desc_end = cron_descriptor(slide.cron_schedule_end) %} + {% set cron_desc_end = cron_descriptor(slide.cron_schedule_end) %} {% if cron_desc_end %} {% if is_valid_cron_date_time(slide.cron_schedule_end) %} {% if slide.is_notification %}