2025-02-09 19:56:41 +08:00
|
|
|
|
<!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>
|
2025-02-11 01:01:14 +08:00
|
|
|
|
<script type="text/javascript" src="assets/js/reconnecting-websocket.min.js"></script>
|
2025-02-09 23:58:06 +08:00
|
|
|
|
<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:
|
2025-02-09 19:56:41 +08:00
|
|
|
|
|
|
|
|
|
<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:
|
|
|
|
|
</body>
|
|
|
|
|
|
|
|
|
|
</html>
|