71 lines
19 KiB
HTML
71 lines
19 KiB
HTML
<!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-block;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> |