Galactic/www/index.html

71 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>UI</title>
<link rel="stylesheet" href="assets/css/sweetalert2.min.css">
<link rel="stylesheet" href="assets/css/iconfont.css">
<script type="text/javascript" src="assets/js/jquery.min.js"></script>
<script type="text/javascript" src="assets/js/vue.min.js"></script>
<script type="text/javascript" src="assets/js/axios.min.js"></script>
<script type="text/javascript" src="assets/js/lodash.min.js"></script>
<script type="text/javascript" src="assets/js/sweetalert2.min.js"></script>
<style>body{user-select:none;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;font-family:Arial,sans-serif;background-color:#f4f4f4;margin:0;padding:0;display:flex;justify-content:center;align-items:center;height:100vh}h1{font-size:22px;color:#555;padding:0 2px}img{-webkit-user-drag:none;user-drag:none;-webkit-user-select:none;user-select:none}button{outline:0}.main{background-color:#fff;padding:8px;border-radius:10px;box-shadow:0 0 10px rgba(0,0,0,.1);width:100%;max-width:480px;height:95%;max-height:960px;text-align:center;align-items:center;overflow:auto}.main::-webkit-scrollbar{width:4px}.main::-webkit-scrollbar-thumb{background-color:#ff6c6c;border-radius:0}.main::-webkit-scrollbar-track{background:#f3f3f3}.bar,.list{align-items:center}.bar{display:flex;justify-content:center}.logo{width:38px;height:38px;padding:2px}.user{padding:10px;margin:10px 5px;text-align:center;cursor:pointer;align-items:center;border-radius:5px;box-shadow:0 2px 4px rgba(0,0,0,.1);transition:all .15s}.user:hover{transform:scale(1.015)}.running{background-image:linear-gradient(45deg,#88e1c5,#9face6);color:#fff;transition:all .35s}.running:hover{margin:10px 5px -10px;box-shadow:0 2px 12px rgba(0,0,0,.35);transition:all .18s}.running:hover .extend{display:block}.stopped{background-image:linear-gradient(45deg,#b3b3b3,#e9ebec);color:#fff;transition:all .35s}.plugins{background-image:linear-gradient(90deg,#fd9a9e,#fadacf);background-size:200% 200%;animation:gradientAnimation 3.5s ease infinite;color:#fff;transition:all .25s}@keyframes gradientAnimation{0%{background-position:0 50%}50%{background-position:100% 50%}to{background-position:0 50%}}.extend{display:none;position:relative;background-color:transparent}.h_div{display:flex;justify-content:space-between;align-items:center}.v_div{display:flex;flex-direction:column}.id{width:172px}.id,.name{display:inline-block;vertical-align:middle;font-family:Arial,sans-serif;margin:0;padding:0;height:auto;text-align:left;font-size:15px;color:#fff;font-weight:700;text-shadow:0 0 1px #ccc;overflow:hidden;white-space:nowrap}.name{cursor:pointer;text-decoration:underline;max-width:200px}.summary{text-align:left;width:160px}.summary,.windows{display:inline-block;vertical-align:middle;font-family:Arial,sans-serif;margin:0;padding:0;height:auto;font-size:14px;color:#fafafa;font-weight:200;text-shadow:0 0 1px #ccc;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-webkit-marquee-style:slide;-webkit-marquee-speed:normal}.windows{text-align:right;width:62px}.h_divider{margin:4px 0;width:100%;height:1px}.h_divider,.v_divider{background-color:hsla(0,0%,100%,.35)}.v_divider{margin:0 4px;width:1px;height:24px}.close{font-family:inherit;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 0 0 #fff;position:relative;right:0;background-color:transparent;border:0;cursor:pointer;opacity:.38}.button{margin:2px 0;padding:10px 24px;font-size:16px;border:none;border-radius:999px;cursor:pointer}.button:disabled{pointer-events:none;filter:saturate(20%);opacity:.25}.run{background-color:#4caf50}.die,.run{padding:6px 12px;font-size:14px;color:#fff;opacity:.95}.die{background-color:#e74c3c}.die-plugin-shutdown{background-color:#f09240}.button:hover{opacity:.8}.desc{display:inline-block;vertical-align:middle;font-family:Arial,sans-serif;margin:2px;padding:0;height:auto;text-align:center;font-size:16px;color:grey;font-weight:200;text-shadow:0 0 1px #ccc;max-width:360px;overflow:hidden;text-overflow:ellipsis;-webkit-marquee-style:slide;-webkit-marquee-speed:normal}.unselectable{user-select:none;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.rollbar{position:fixed;right:10px;bottom:10px;z-index:50;display:block;width:auto}.rollbar .rollbar-item{position:relative;margin-top:10px;width:40px;height:40px;background-color:#666;color:#fff;text-align:center;line-height:1.4;opacity:.5;cursor:pointer;transition:opacity .5s;-webkit-transition:all .3s ease-in-out;-moz-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.rollbar .rollbar-item:hover{color:#fff;opacity:.9}.iconfont{font-size:1rem}.iconfont,.iconfont-small{vertical-align:middle;font-style:normal;font-family:iconfont!important;-webkit-font-smoothing:antialiased;-webkit-text-stroke-width:.2px;-moz-osx-font-smoothing:grayscale}.iconfont-small{font-size:.875rem}.iconfont-big{font-size:1.1rem}.iconfont-big,.iconfont-x-big{vertical-align:middle;font-style:normal;font-family:iconfont!important;-webkit-font-smoothing:antialiased;-webkit-text-stroke-width:.2px;-moz-osx-font-smoothing:grayscale}.iconfont-x-big{font-size:1.875rem}.btn-group{text-align:left;margin:15px 0;display:flex;flex-wrap:wrap}.btn-group .text-item{vertical-align:middle;font-family:Arial,sans-serif;margin:0;padding:2px 4px;height:auto;font-size:13px;text-shadow:0 0 1px #ccc;width:88%}.btn-group .btn,.btn-group .text-item{display:inline;text-align:left;color:#fff;overflow:hidden;white-space:nowrap}.btn-group .btn{max-width:50%;margin:2px;background-color:rgba(0,0,0,.15);border:1px solid hsla(0,0%,100%,0);border-radius:4px;padding:12px;text-decoration:none;font-size:14px;cursor:pointer;text-overflow:ellipsis;flex:0 0 calc(50% - 4px);box-sizing:border-box}.btn-group .btn:hover{background-color:hsla(0,0%,100%,.05);border:1px solid hsla(0,0%,100%,.95)}.btn-group .btn:active{background-color:hsla(0,0%,100%,.15);border:1px solid hsla(0,0%,100%,.95)}@media (max-width:540px){.main{width:100%;height:98%;padding:6px}.id{width:143px}.name{max-width:185px}.summary{width:131px}}</style></head>
<body>
<div class="main" id="app">
<div class="bar">
<img class="logo" src="assets/images/logo-chrome.png">
<h1 class="unselectable">用户管理</h1>
</div>
<div class="list" id="userList">
<div v-for="(item, key) in userList" :key="key">
<div :class="{'user': true, 'running': item.is_running && !item.current_plugin_executing, 'stopped': !item.is_running && !item.current_plugin_executing, 'plugins': item.current_plugin_executing}" @dblclick="toggleTab(item.user_id, $event)">
<div class="v_div">
<div class="h_div">
<div class="h_div">
<p class="unselectable id">{{ item.user_id }}</p>
<p class="unselectable name" @click="userSetName(item.user_id, $event)" v-if="!item.user_name || item.user_name.trim() === ''">未命名</p>
<p class="unselectable name" @click="userSetName(item.user_id, $event)" v-else>{{ item.user_name }}</p>
</div>
<a class="close" @click="userDel(item.user_id, $event)"><span class="unselectable" aria-hidden="true">×</span></a>
</div>
<div class="h_divider"></div>
<div class="h_div">
<div class="h_div">
<p class="unselectable summary" v-if="item.is_running !== true">未运行</p>
<p class="unselectable summary" v-else>{{ item.alert || item.title }}</p>
<p class="unselectable windows" v-if="item.is_running !== true"></p>
<p class="unselectable windows" v-else>{{ !item.window_handles ? '0个页面' : item.window_handles.length + '个页面' }}</p>
</div>
<div class="v_divider"></div>
<div class="unselectable">
<button class="button unselectable run" :disabled="item.is_running == 1" @click="userRun(item.user_id, $event)">运行</button>
<button v-if="!item.current_plugin_executing" class="button unselectable die" :disabled="item.is_running == 0" @click="userDie(item.user_id, $event)">停止</button>
<button v-else class="button unselectable die die-plugin-shutdown" :disabled="item.is_running == 0" @click="pluginShutdown(item.user_id, $event)">停止</button>
</div>
</div>
</div>
<div class="extend">
<div class="btn-group">
<button v-for="(plugin_id, plugin_index) in Object.keys(item.plugins).sort()" :key="plugin_index" class="btn" @click.stop="pluginRun(item.user_id, plugin_id, item.plugins[plugin_id].requirements, $event)"><i class="iconfont top-flashlight_fill"></i><p class="text-item">{{ item.plugins[plugin_id].name }}</p></button>
</div>
</div>
</div>
</div>
</div>
<p class="desc unselectable" v-if="Object.keys(userList).length === 0">暂时还没有用户,请先添加~</p>
<button class="button unselectable" id="addUser" @click="userAdd($event)"></button>
</div>
<div class="rollbar"><div class="rollbar-item" id="back-to-top" style="height:0" title="返回顶部"><i class="iconfont-x-big top-top"></i></div></div>
<script>window.onload=function(){window.addEventListener('mousewheel',function(event){if(event.ctrlKey===true||event.metaKey){event.preventDefault()}},{passive:false});document.addEventListener('keydown',function(event){if((event.ctrlKey===true||event.metaKey===true)&&(event.which===61||event.which===107||event.which===173||event.which===109||event.which===187||event.which===189)){event.preventDefault()}},false)}</script>
<script>window.addEventListener("load",function(){let backBtn=document.getElementById("back-to-top");let bodyObj=document.getElementById("app");let hiddenControl=function(element,isHidden){if(isHidden){backBtn.style.height="0"}else{backBtn.style.height="40px"}};bodyObj.addEventListener("scroll",function(){if(bodyObj.scrollTop>bodyObj.offsetHeight||bodyObj.scrollTop>300){hiddenControl(backBtn,0)}else{hiddenControl(backBtn,1)}});backBtn.addEventListener("click",function(e){e.preventDefault();bodyObj.scrollTo({top:0,behavior:"smooth"})})});</script>
<script>const Toast=Swal.mixin({toast:true,position:"center",showConfirmButton:false,timer:2250,timerProgressBar:false,});const Backs=Swal.mixin({html:"",timer:750,timerProgressBar:true,showConfirmButton:false,allowOutsideClick:false,});const FileUpload=Swal.mixin({text:"打开文件",input:"file",inputAttributes:{accept:"*"},inputValidator:(value)=>{if(value===null){return"还未选择文件"}},showConfirmButton:true,showCancelButton:true,confirmButtonText:"确定",cancelButtonText:"取消",allowOutsideClick:false,showLoaderOnConfirm:true,preConfirm:(files)=>{if(!(files instanceof FileList)){files=[files]}let formData=new FormData();for(let i=0;i<files.length;i++){formData.append("files",files[i])}return axios.post("./upload_files",formData).then((response)=>{if(response.data.code!=0){throw new Error(response.data.info);}return response.data.data}).catch((error)=>{Swal.showValidationMessage(error);return false})},});const MultFileUpload=Swal.mixin({text:"打开文件",input:"file",inputAttributes:{multiple:"multiple",accept:"*"},inputValidator:(value)=>{if(value===null){return"还未选择文件"}},showConfirmButton:true,showCancelButton:true,confirmButtonText:"确定",cancelButtonText:"取消",allowOutsideClick:false,showLoaderOnConfirm:true,preConfirm:(files)=>{if(!(files instanceof FileList)){files=[files]}let formData=new FormData();for(let i=0;i<files.length;i++){formData.append("files",files[i])}return axios.post("./upload_files",formData).then((response)=>{if(response.data.code!=0){throw new Error(response.data.info);}return response.data.data}).catch((error)=>{Swal.showValidationMessage(error);return false})},});const TextUpload=Swal.mixin({text:"输入内容",input:"text",showConfirmButton:true,showCancelButton:true,confirmButtonText:"确定",cancelButtonText:"取消",allowOutsideClick:false,});const MultTextUpload=Swal.mixin({text:"输入内容",input:"textarea",showConfirmButton:true,showCancelButton:true,confirmButtonText:"确定",cancelButtonText:"取消",allowOutsideClick:false,});const MixinInput=async function(requirements){if(Array.isArray(requirements)){let inputClassList={"file":FileUpload,"file-mult":MultFileUpload,"text":TextUpload,"text-mult":MultTextUpload,};let _reqs=requirements;let _lens=requirements.length;let _results=[];for(let i=0;i<_lens;i++){let inputClass=inputClassList[_reqs[i]["type"]];if(!inputClass){Toast.fire({icon:"error",title:"不支持的输入类型"});return null}let result=await inputClass.fire({text:_reqs[i]["html"],});if(!result.isConfirmed){return null}else{_results.push(result.value)}}return _results}else{return[]}};new Vue({el:"#app",data:{userList:{},isConnected:true},created(){this.fetchData();setInterval(this.fetchData,1750)},methods:{fetchData:_.throttle(function(){axios.get("./user_all_details").then((response)=>{let newData=response.data;for(let userId in this.userList){if(!newData[userId]){Vue.delete(this.userList,userId)}}for(let userId in newData){Vue.set(this.userList,userId,newData[userId])}}).catch((error)=>{});axios.get("./200").then((response)=>{this.isConnected=true}).catch((error)=>{if(this.isConnected){Swal.fire({title:"连接错误",text:"请检查后端服务是否已运行",icon:"error",confirmButtonText:"确定",allowOutsideClick:false,})}this.isConnected=false})},1000),toggleTab(user_id,event){event.stopPropagation();if(this.userList[user_id].is_running){this.userFocusWindow(user_id,event)}else{this.userRun(user_id,event)}},userFocusWindow(user_id,event){event.stopPropagation();axios.post("./user_focus_window",{data:{user_id:user_id}},{headers:{"Content-Type":"application/json"}},).then((response)=>{let data=response.data;if(data.code!==0){Toast.fire({icon:"warning",title:data.info})}}).catch((error)=>{Swal.fire({title:"请求失败",text:error,icon:"error",confirmButtonText:"确定",allowOutsideClick:false,})})},pluginRun(user_id,plugin_id,requirements,event){event.stopPropagation();Backs.fire({html:"正在加载...",timer:250});MixinInput(requirements).then((result)=>{if(result===null){return null}axios.post("./plugin_run",{data:{user_id:user_id,plugin_id:plugin_id,requirements:result,},},{headers:{"Content-Type":"application/json"}},).then((response)=>{let data=response.data;if(data.code!==0){Toast.fire({icon:"warning",title:data.info})}}).catch((error)=>{Swal.fire({title:"请求失败",text:error,icon:"error",confirmButtonText:"确定",allowOutsideClick:false,})})})},pluginShutdown(user_id,event){event.stopPropagation();Backs.fire({html:"正在结束...",timer:250});axios.post("./plugin_shutdown",{data:{user_id:user_id}},{headers:{"Content-Type":"application/json"}},).then((response)=>{let data=response.data;if(data.code!==0){Toast.fire({icon:"warning",title:data.info})}}).catch((error)=>{Swal.fire({title:"请求失败",text:error,icon:"error",confirmButtonText:"确定",allowOutsideClick:false,})});this.fetchData()},userRun(user_id,event){event.stopPropagation();Backs.fire({html:"正在启动...",timer:1500});axios.post("./user_run",{data:{user_id:user_id}},{headers:{"Content-Type":"application/json"}},).then((response)=>{let data=response.data;if(data.code!==0){Toast.fire({icon:"warning",title:data.info})}}).catch((error)=>{Swal.fire({title:"请求失败",text:error,icon:"error",confirmButtonText:"确定",allowOutsideClick:false,})});this.fetchData()},userDie(user_id,event){event.stopPropagation();Backs.fire({html:"正在关闭...",timer:2500});axios.post("./user_die",{data:{user_id:user_id}},{headers:{"Content-Type":"application/json"}},).then((response)=>{let data=response.data;if(data.code!==0){Toast.fire({icon:"warning",title:data.info})}}).catch((error)=>{Swal.fire({title:"请求失败",text:error,icon:"error",confirmButtonText:"确定",allowOutsideClick:false,})});this.fetchData()},userAdd(event){event.stopPropagation();Backs.fire({html:"请稍候..."});axios.post("./user_add",{data:{user_id:"u"+String(new Date().getTime())}},{headers:{"Content-Type":"application/json"}},).then((response)=>{let data=response.data;if(data.code!==0){Toast.fire({icon:"warning",title:data.info})}}).catch((error)=>{Swal.fire({title:"请求失败",text:error,icon:"error",confirmButtonText:"确定",allowOutsideClick:false,})});this.fetchData()},userDel(user_id,event){event.stopPropagation();Swal.fire({title:"",text:"确认删除用户数据?",icon:"warning",showCancelButton:true,confirmButtonText:"确定",cancelButtonText:"取消",allowOutsideClick:false,}).then((result)=>{if(result.isConfirmed){Backs.fire({html:"请稍候..."});axios.post("./user_del",{data:{user_id:user_id}},{headers:{"Content-Type":"application/json"}},).then((response)=>{let data=response.data;if(data.code!==0){Toast.fire({icon:"warning",title:data.info})}}).catch((error)=>{Swal.fire({title:"请求失败",text:error,icon:"error",confirmButtonText:"确定",allowOutsideClick:false,})});this.fetchData()}})},userSetName(user_id,event){event.stopPropagation();Swal.fire({input:"text",inputLabel:"修改名称",showCancelButton:true,confirmButtonText:"确定",cancelButtonText:"取消",allowOutsideClick:false,inputValue:this.userList[user_id].user_name,inputValidator:(value)=>{if(value==""){return"名称不能为空"}if(value.includes("<")||value.includes(">")){return"含有非法字符"}if(value.length>24){return"长度超出限制"}},}).then((result)=>{if(result.isConfirmed){let inputValue=result.value;Backs.fire({html:"请稍候..."});axios.post("./user_set_name",{data:{user_id:user_id,user_name:inputValue.trim()}},{headers:{"Content-Type":"application/json"}},).then((response)=>{let data=response.data;if(data.code!==0){Toast.fire({icon:"warning",title:data.info})}}).catch((error)=>{Swal.fire({title:"请求失败",text:error,icon:"error",confirmButtonText:"确定",allowOutsideClick:false,})});this.fetchData()}})},},});</script>
</body>
</html>