├── .gitignore ├── API文档.md ├── README.assets ├── image-20200509104046360.png ├── image-20200516100948576.png ├── image-20200521151342244.png ├── image-20200525205201420.png ├── image-20200525205235288.png ├── image-20200525205309309.png ├── image-20200525205526117.png ├── image-20200525205554646.png ├── image-20200525210110429.png ├── image-20200525210121127.png ├── image-20200525210138367.png ├── image-20200525210152084.png ├── image-20200525210249706.png ├── image-20200525210550651.png └── 软件工程综合实习.png ├── README.md ├── config └── config.yaml ├── db ├── init.py └── init.sql ├── deploy.yaml ├── git_push.bat ├── git_push.sh ├── main.py ├── model ├── SocketIOWatch.py ├── __init__.py ├── crontab_manager.py ├── docker_manager.py ├── file_manager.py ├── ftp_manager.py ├── nginx_manager.py ├── oss.py ├── process_manager.py ├── sql_func.py ├── system_group.py ├── system_info.py ├── system_user.py └── ufw_manager.py ├── requirements.txt ├── route ├── __init__.py ├── crontab_manager.py ├── dns.py ├── docker_manager.py ├── file_manager.py ├── ftp_manager.py ├── login.py ├── nginx_manager.py ├── parameter.py ├── process_manager.py ├── system_info.py ├── system_user.py └── ufw_manager.py ├── script ├── create_website.sh ├── install_crontab.sh ├── install_ftp.sh ├── install_nginx.sh └── install_ufw.sh ├── static ├── css │ ├── file.css │ └── login.css ├── favicon.ico ├── html │ ├── crontab.html │ ├── docker.html │ ├── file.html │ ├── ftp.html │ ├── login.html │ ├── nginx.html │ ├── process.html │ └── ufw.html ├── icon │ ├── container.png │ ├── dir.png │ ├── html.png │ ├── nonefile.png │ └── pic.png ├── image │ └── bg.jpg ├── index.html ├── js │ ├── crontab.js │ ├── docker.js │ ├── file.js │ ├── ftp.js │ ├── index.js │ ├── login.js │ ├── nginx.js │ ├── process.js │ └── ufw.js ├── lib │ ├── css │ │ ├── bootstrap.css │ │ ├── bootstrap.min.css │ │ ├── global.css │ │ ├── plugins │ │ │ └── morris.css │ │ └── sb-admin.css │ ├── font-awesome │ │ ├── css │ │ │ ├── font-awesome.css │ │ │ └── font-awesome.min.css │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ └── fontawesome-webfont.woff │ │ ├── less │ │ │ ├── bordered-pulled.less │ │ │ ├── core.less │ │ │ ├── fixed-width.less │ │ │ ├── font-awesome.less │ │ │ ├── icons.less │ │ │ ├── larger.less │ │ │ ├── list.less │ │ │ ├── mixins.less │ │ │ ├── path.less │ │ │ ├── rotated-flipped.less │ │ │ ├── spinning.less │ │ │ ├── stacked.less │ │ │ └── variables.less │ │ └── scss │ │ │ ├── _bordered-pulled.scss │ │ │ ├── _core.scss │ │ │ ├── _fixed-width.scss │ │ │ ├── _icons.scss │ │ │ ├── _larger.scss │ │ │ ├── _list.scss │ │ │ ├── _mixins.scss │ │ │ ├── _path.scss │ │ │ ├── _rotated-flipped.scss │ │ │ ├── _spinning.scss │ │ │ ├── _stacked.scss │ │ │ ├── _variables.scss │ │ │ └── font-awesome.scss │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ └── js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ ├── echarts.min.js │ │ ├── jquery.js │ │ ├── mock-min.js │ │ ├── plugins │ │ ├── flot │ │ │ ├── excanvas.min.js │ │ │ ├── flot-data.js │ │ │ ├── jquery.flot.js │ │ │ ├── jquery.flot.pie.js │ │ │ ├── jquery.flot.resize.js │ │ │ └── jquery.flot.tooltip.min.js │ │ └── morris │ │ │ ├── morris-data.js │ │ │ ├── morris.js │ │ │ ├── morris.min.js │ │ │ └── raphael.min.js │ │ └── vue.min.js └── mock │ ├── file.js │ ├── index.js │ └── nginx.js └── test ├── __init__.py ├── test_docker_manager.py ├── test_file_manager.py └── test_system_info.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .idea/* 3 | ./temp/* 4 | *.db 5 | deploy.retry 6 | config/oss.yaml 7 | ~/* -------------------------------------------------------------------------------- /API文档.md: -------------------------------------------------------------------------------- 1 | # 1. 系统信息 2 | 3 | ## 1.1 获取系统信息 4 | 5 | 1. url: /api/sysinfo 6 | 2. method: get 7 | 3. parameter: None 8 | 4. response 9 | 10 | ```json 11 | { 12 | "cpu_info": { 13 | "core_num": 2, 14 | "cpu_num": 1, 15 | "model_name": "Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz", 16 | "processor_num": 2 17 | }, 18 | "disk_info": { 19 | "Total": 39, 20 | "Used": 29 21 | }, 22 | "io_info": { 23 | "io_recv": 0, 24 | "io_sent": 0, 25 | "net_io_recv": 0, 26 | "net_io_sent": 0 27 | }, 28 | "mem_info": { 29 | "Active": 2.71, 30 | "-Buffers": 0.3, 31 | "-Cached": 0.75, 32 | "MemAvailable": 1.4, 33 | "-MemFree": 0.51, 34 | "MemTotal": 3.85, 35 | "SwapCached": 0, 36 | "-Used": 2.29 37 | } 38 | } 39 | ``` 40 | 41 | # 2. DNS 42 | 43 | ## 2.1 获取现在的dns 44 | 45 | 1. url: /api/dns 46 | 47 | 2. method: get 48 | 49 | 3. parameter: None 50 | 51 | 4. response: 52 | 53 | ```json 54 | { 55 | "server_1": "8.8.8.8", 56 | "server_2": "8.8.4.4" 57 | } 58 | ``` 59 | 60 | 5. description: 获取当前服务器的dns server 61 | 62 | ## 2.2 修改DNS 63 | 64 | 1. url: /api/dns 65 | 66 | 2. method: post 67 | 68 | 3. parameter: application/json 69 | 70 | ```json 71 | { 72 | "server_1": "8.8.8.8", 73 | "server_2": "8.8.8.8" 74 | } 75 | ``` 76 | 77 | 4. response: 78 | 79 | ```cpp 80 | { 81 | "status": "OK" 82 | } 83 | ``` 84 | 85 | 5. description: 提交用户希望变更的dns server 86 | 87 | # 3. 登录 & 登出 88 | 89 | ## 3.1 登录 90 | 91 | 1. url: /api/login 92 | 93 | 2. method: post 94 | 95 | 3. parameter: 96 | 97 | ```json 98 | { 99 | "name": "admin", 100 | "passwd": "admin" 101 | } 102 | ``` 103 | 104 | 4. response 105 | 106 | ```json 107 | { 108 | "status": "OK" 109 | } 110 | ``` 111 | 112 | ## 3.2 登出 113 | 114 | 1. url: /api/logout 115 | 116 | 2. method: post 117 | 118 | 3. parameter: None 119 | 120 | 4. response: 121 | 122 | ```json 123 | { 124 | "status": "OK" 125 | } 126 | ``` 127 | 128 | 129 | # 4. 文件管理器 130 | 131 | ## 4.1 获取目录信息 132 | 133 | 1. url: /api/file 134 | 135 | 2. method: get 136 | 137 | 3. parameter: 138 | 139 | ```json 140 | { 141 | "path": "/root" 142 | } 143 | ``` 144 | 145 | 4. response: 146 | 147 | ```json 148 | { 149 | "file": [ 150 | { 151 | "file_type": "dir", 152 | "mode": "755", 153 | "name": ".", 154 | "owner": "root" 155 | }, 156 | { 157 | "file_type": "dir", 158 | "mode": "755", 159 | "name": "..", 160 | "owner": "root" 161 | }, 162 | { 163 | "file_type": "dir", 164 | "mode": "755", 165 | "name": "bin", 166 | "owner": "root" 167 | }, 168 | { 169 | "file_type": "dir", 170 | "mode": "755", 171 | "name": "boot", 172 | "owner": "root" 173 | }, 174 | { 175 | "file_type": "dir", 176 | "mode": "755", 177 | "name": "CONFIG_DIR", 178 | "owner": "root" 179 | }, 180 | { 181 | "file_type": "dir", 182 | "mode": "755", 183 | "name": "dev", 184 | "owner": "root" 185 | }, 186 | { 187 | "file_type": "dir", 188 | "mode": "755", 189 | "name": "DOWNLOAD_DIR", 190 | "owner": "root" 191 | } 192 | ] 193 | } 194 | ``` 195 | 196 | -------------------------------------------------------------------------------- /README.assets/image-20200509104046360.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/README.assets/image-20200509104046360.png -------------------------------------------------------------------------------- /README.assets/image-20200516100948576.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/README.assets/image-20200516100948576.png -------------------------------------------------------------------------------- /README.assets/image-20200521151342244.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/README.assets/image-20200521151342244.png -------------------------------------------------------------------------------- /README.assets/image-20200525205201420.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/README.assets/image-20200525205201420.png -------------------------------------------------------------------------------- /README.assets/image-20200525205235288.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/README.assets/image-20200525205235288.png -------------------------------------------------------------------------------- /README.assets/image-20200525205309309.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/README.assets/image-20200525205309309.png -------------------------------------------------------------------------------- /README.assets/image-20200525205526117.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/README.assets/image-20200525205526117.png -------------------------------------------------------------------------------- /README.assets/image-20200525205554646.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/README.assets/image-20200525205554646.png -------------------------------------------------------------------------------- /README.assets/image-20200525210110429.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/README.assets/image-20200525210110429.png -------------------------------------------------------------------------------- /README.assets/image-20200525210121127.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/README.assets/image-20200525210121127.png -------------------------------------------------------------------------------- /README.assets/image-20200525210138367.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/README.assets/image-20200525210138367.png -------------------------------------------------------------------------------- /README.assets/image-20200525210152084.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/README.assets/image-20200525210152084.png -------------------------------------------------------------------------------- /README.assets/image-20200525210249706.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/README.assets/image-20200525210249706.png -------------------------------------------------------------------------------- /README.assets/image-20200525210550651.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/README.assets/image-20200525210550651.png -------------------------------------------------------------------------------- /README.assets/软件工程综合实习.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/README.assets/软件工程综合实习.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Linux Control Panel 2 | 3 | [![bilibili 演示](https://img.shields.io/badge/bilibili-演示-EE6F98)](https://www.bilibili.com/video/BV16z411v7D2/) 4 | 5 | [![Build Status](http://chunibyo.xyz:8085/buildStatus/icon?job=tapd)](http://chunibyo.xyz:8085/job/tapd/) 6 | 7 | ## :one: How To Use 8 | 9 | 1. 配置 10 | 11 | ```bash 12 | sh script/install_* 13 | 14 | virtualenv venv 15 | venv/bin/python main.py 16 | ``` 17 | 18 | 2. `config`目录下新建`oss.yaml` 19 | 20 | ```yaml 21 | oss: 22 | Auth: 23 | AccessKeyId: XXXXXXXXXX 24 | AccessKeySecret: XXXXXXXXXXX 25 | Bucket: 26 | EndPoint: XXXXXXXXXXX 27 | BucketName: XXXXXXXXXXXX 28 | ``` 29 | 30 | ## :two: 功能介绍 31 | 32 | ### 2.1 资源监控 33 | 34 | ![image-20200525205201420](README.assets/image-20200525205201420.png) 35 | 36 | ### 2.2 进程管理(每秒刷新) 37 | 38 | ![image-20200525205235288](README.assets/image-20200525205235288.png) 39 | 40 | ### 2.3 容器管理 41 | 42 | ![image-20200525205309309](README.assets/image-20200525205309309.png) 43 | 44 | ### 2.4 FTP服务 45 | 46 | ![image-20200525205526117](README.assets/image-20200525205526117.png) 47 | 48 | ### 2.5 文件管理 49 | 50 | ![image-20200525205554646](README.assets/image-20200525205554646.png) 51 | 52 | ### 2.6 网站管理 53 | 54 | ![image-20200525210110429](README.assets/image-20200525210110429.png) 55 | 56 | ![image-20200525210121127](README.assets/image-20200525210121127.png) 57 | 58 | ![image-20200525210138367](README.assets/image-20200525210138367.png) 59 | 60 | ![image-20200525210152084](README.assets/image-20200525210152084.png) 61 | 62 | ### 2.7 定时任务 63 | 64 | ![image-20200525210249706](README.assets/image-20200525210249706.png) 65 | 66 | ![image-20200525210550651](README.assets/image-20200525210550651.png) 67 | 68 | ## :three: 后端功能的实现 69 | 70 | ### 3.1 系统管理 71 | 72 | #### 3.1.1 DNS设置 73 | 74 | ```bash 75 | vim /etc/resolvconf/resolv.conf.d/base 76 | ``` 77 | 78 | ![image-20200509104046360](README.assets/image-20200509104046360.png) 79 | 80 | #### 3.1.2 用户管理 81 | 82 | 1. 创建user 83 | 84 | ```bash 85 | useradd -m chunibyo 86 | # -m 创建/home目录 87 | ``` 88 | 89 | 2. 设置user密码 90 | 91 | ```bash 92 | passwd chunibyo 93 | ``` 94 | 95 | 96 | 97 | 3. 删除user 98 | 99 | ```bash 100 | userdel chunibyo 101 | ``` 102 | 103 | 4. 创建group 104 | 105 | ```bash 106 | groupadd test_group 107 | ``` 108 | 109 | 5. 删除group 110 | 111 | ```bash 112 | groupdel test_group 113 | ``` 114 | 115 | 6. 添加user到group 116 | 117 | ```bash 118 | usermod -G test_group chunibyo 119 | ``` 120 | 121 | 7. 删除user从group 122 | 123 | ```bash 124 | gpasswd -d chunibyo test_group 125 | ``` 126 | 127 | ### 3.2 docker 128 | 129 | ```bash 130 | docker run --name some-mysql --restart=always -e MYSQL_ROOT_PASSWORD=foo -d mysql:latest 131 | 132 | docker run --name some-nginx --restart=unless-stopped -d nginx 133 | 134 | docker run --name some-redis --restart=unless-stopped -d redis 135 | ``` 136 | 137 | ### 3.3 网络安全 138 | 139 | 1. **添加**拒绝8888端口的tcp连接 140 | 141 | ```bash 142 | sudo iptables -t filter -A INPUT -j DROP -p tcp --dport 8888 143 | ``` 144 | 145 | ![image-20200521151342244](README.assets/image-20200521151342244.png) 146 | 147 | 2. **删除**拒绝8888端口的tcp连接 148 | 149 | 150 | ## :four: ​DevOps工作流 151 | 152 | ![软件工程综合实习](README.assets/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B%E7%BB%BC%E5%90%88%E5%AE%9E%E4%B9%A0.png) 153 | 154 | ### 4.1 [SonarQube](https://www.fosstechnix.com/install-sonarqube-on-ubuntu/#step-3-download-and-install-sonarqube-on-ubuntu) 155 | 156 | 1. MySQL建库 157 | 158 | ```sql 159 | create database sonarqube character set utf8 collate utf8_general_ci; 160 | ``` 161 | 162 | 2. 建立 `sonar` 用户(因为mysql在容器里面并且没有开放端口, 所以直接监听公网最方便) 163 | 164 | ```sql 165 | create user 'sonar'@'%' identified by 'foo'; 166 | 167 | grant all privileges on sonar.* to 'sonar'@'%' ; 168 | 169 | flush privileges; 170 | ``` 171 | 172 | 3. 安装sonar 173 | 174 | 4. 打包成service 175 | 176 | ``` 177 | /lib/systemd/system/sonar.service 178 | 179 | [Unit] 180 | Description=SonarQube service 181 | After=syslog.target network.target 182 | 183 | [Service] 184 | Type=forking 185 | 186 | ExecStart=/opt/sonarqube/bin/linux-x86-64/sonar.sh start 187 | ExecStop=/opt/sonarqube/bin/linux-x86-64/sonar.sh stop 188 | 189 | User=sonar 190 | Group=sonar 191 | Restart=always 192 | 193 | LimitNOFILE=65536 194 | LimitNPROC=4096 195 | 196 | 197 | [Install] 198 | WantedBy=multi-user.target 199 | ``` 200 | 201 | ![image-20200516100948576](README.assets/image-20200516100948576.png) 202 | 203 | ### 4.2 Ansible 204 | 205 | `apt`仓库默认安装 206 | 207 | ```yaml 208 | --- 209 | - hosts: cugergz.chunibyo.xyz 210 | vars: 211 | repo_folder: /root/Panel 212 | remote_user: root 213 | tasks: 214 | 215 | - name: "Create directory if not exists" 216 | file: 217 | path: "{{ repo_folder }}" 218 | state: directory 219 | mode: 0755 220 | group: root 221 | owner: root 222 | 223 | - name: kill old process 224 | shell: ps aux | grep main.p[y] | awk '{print $2}' | xargs kill 225 | ignore_errors: true 226 | 227 | - name: clone repository 228 | git: 229 | repo: https://github.com/chunibyo-wly/LinuxControlPanel.git 230 | dest: "{{ repo_folder }}" 231 | update: yes 232 | force: yes 233 | 234 | - name: create python virtualenv 235 | pip: 236 | requirements: "{{ repo_folder }}/requirements.txt" 237 | virtualenv: "{{ repo_folder }}/venv" 238 | virtualenv_command: /usr/bin/python3 -m venv 239 | 240 | - name: install ftp 241 | shell: "sh {{ repo_folder }}/script/install_ftp.sh" 242 | 243 | - name: install nginx 244 | shell: "sh {{ repo_folder }}/script/install_nginx.sh" 245 | 246 | - name: install cron 247 | shell: "sh {{ repo_folder }}/script/install_crontab.sh" 248 | 249 | - name: create database 250 | shell: "cd {{ repo_folder }} && sudo venv/bin/python db/init.py" 251 | 252 | - name: start server 253 | shell: "cd {{ repo_folder }} && nohup sudo {{ repo_folder }}/venv/bin/python main.py > /tmp/LinuxControlPanel.log 2>&1 &" 254 | ``` 255 | 256 | -------------------------------------------------------------------------------- /config/config.yaml: -------------------------------------------------------------------------------- 1 | user: 2 | name: admin 3 | passwd: admin -------------------------------------------------------------------------------- /db/init.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sqlite3 3 | import sys 4 | 5 | sys.path.append(os.path.join(os.getcwd())) 6 | print(sys.path) 7 | 8 | conn = sqlite3.connect('db/Linux.db') 9 | cursor = conn.cursor() 10 | 11 | cursor.executescript(open('db/init.sql', 'r').read()) 12 | 13 | conn.commit() 14 | conn.close() 15 | -------------------------------------------------------------------------------- /db/init.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS nginx 2 | ( 3 | name TEXT NOT NULL, 4 | port INT NOT NULL 5 | ); 6 | 7 | CREATE TABLE IF NOT EXISTS crontab 8 | ( 9 | cron_id INTEGER PRIMARY KEY AUTOINCREMENT, 10 | cron_type TEXT NOT NULL, 11 | command TEXT NOT NULL, 12 | description TEXT NOT NULL 13 | ); 14 | 15 | CREATE TABLE IF NOT EXISTS ufw_port 16 | ( 17 | rule_id INTEGER PRIMARY KEY AUTOINCREMENT, 18 | port INTEGER NOT NULL, 19 | protocol TEXT NOT NULL, 20 | description TEXT NOT NULL 21 | ); 22 | 23 | CREATE TABLE IF NOT EXISTS ufw_ip 24 | ( 25 | rule_id INTEGER PRIMARY KEY AUTOINCREMENT, 26 | ip TEXT NOT NULL, 27 | protocol TEXT NOT NULL, 28 | description TEXT NOT NULL 29 | ); -------------------------------------------------------------------------------- /deploy.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: cugergz.chunibyo.xyz 3 | vars: 4 | repo_folder: /root/Panel 5 | remote_user: root 6 | tasks: 7 | 8 | - name: "Create directory if not exists" 9 | file: 10 | path: "{{ repo_folder }}" 11 | state: directory 12 | mode: 0755 13 | group: root 14 | owner: root 15 | 16 | - name: kill old process 17 | shell: ps aux | grep main.p[y] | awk '{print $2}' | xargs kill 18 | ignore_errors: true 19 | 20 | - name: clone repository 21 | git: 22 | repo: https://github.com/chunibyo-wly/LinuxControlPanel.git 23 | dest: "{{ repo_folder }}" 24 | update: yes 25 | force: yes 26 | version: master 27 | 28 | - name: create python virtualenv 29 | pip: 30 | requirements: "{{ repo_folder }}/requirements.txt" 31 | virtualenv: "{{ repo_folder }}/venv" 32 | virtualenv_command: /usr/bin/python3 -m venv 33 | 34 | - name: install ftp 35 | shell: "sh {{ repo_folder }}/script/install_ftp.sh" 36 | 37 | - name: install nginx 38 | shell: "sh {{ repo_folder }}/script/install_nginx.sh" 39 | 40 | - name: install cron 41 | shell: "sh {{ repo_folder }}/script/install_crontab.sh" 42 | 43 | - name: create database 44 | shell: "cd {{ repo_folder }} && sudo venv/bin/python db/init.py" 45 | 46 | - name: install ufw 47 | shell: "sh {{ repo_folder }}/script/install_ufw.sh" 48 | 49 | - name: start server 50 | shell: "cd {{ repo_folder }} && nohup sudo {{ repo_folder }}/venv/bin/python main.py > /tmp/LinuxControlPanel.log 2>&1 &" 51 | 52 | 53 | -------------------------------------------------------------------------------- /git_push.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/git_push.bat -------------------------------------------------------------------------------- /git_push.sh: -------------------------------------------------------------------------------- 1 | git add . 2 | git commit --allow-empty -m "--story="$1" --user=陈彦兵" 3 | git commit --allow-empty -m "--story="$1" --user=cuggz" 4 | git commit --allow-empty -m "--story="$1" --user=吴龙永" 5 | git push origin dev-two -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #! ./venv/bin/python 2 | 3 | from flask_cors import CORS 4 | from flask import Flask, request, redirect 5 | 6 | app = Flask(__name__, static_url_path='') 7 | CORS(app, supports_credentials=True) 8 | 9 | 10 | @app.route('/') 11 | def hello_world(): 12 | if 'login' not in request.cookies: 13 | return redirect('html/login.html', code=302) 14 | return app.send_static_file('index.html') 15 | 16 | 17 | if __name__ == '__main__': 18 | from route import * 19 | 20 | app.run( 21 | debug=True, 22 | host='0.0.0.0', 23 | port=8888 24 | ) 25 | -------------------------------------------------------------------------------- /model/SocketIOWatch.py: -------------------------------------------------------------------------------- 1 | from model.system_info import SystemWatch 2 | 3 | 4 | def SocketIOSystemWatch(socketio): 5 | while True: 6 | socketio.sleep(1) 7 | socketio.emit( 8 | 'sys_info', { 9 | 'cpu_info': SystemWatch.get_cpu_info(), 10 | 'mem_info': SystemWatch.get_mem_info(), 11 | 'disk_info': SystemWatch.get_disk_info(), 12 | 'io_info': SystemWatch.get_io_info() 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/model/__init__.py -------------------------------------------------------------------------------- /model/crontab_manager.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import getpass 3 | 4 | import sqlite3 5 | from subprocess import getoutput 6 | from crontab import CronTab 7 | 8 | from model.sql_func import execute, insert 9 | 10 | 11 | class CrontabManager: 12 | def __init__(self, cron_type=None): 13 | self.cron_type = cron_type 14 | self.cron = CronTab(user=getpass.getuser()) 15 | 16 | def _type2str(self): 17 | if self.cron_type == "minute": 18 | return "* * * * *" 19 | elif self.cron_type == "hour": 20 | return "0 * * * *" 21 | elif self.cron_type == "day": 22 | return "0 0 * * *" 23 | elif self.cron_type == "week": 24 | return "0 0 * * {}".format(datetime.today().weekday() + 1) 25 | elif self.cron_type == "month": 26 | return "0 0 {} * *".format(datetime.today().day) 27 | 28 | def insert_cron(self, command, description): 29 | rowid = insert( 30 | "INSERT INTO crontab (cron_type, command, description) " 31 | "VALUES (?, ?, ?);", 32 | (self.cron_type, command, description) 33 | ) 34 | 35 | task = self.cron.new(command) 36 | task.setall(self._type2str()) 37 | task.set_comment(str(rowid)) 38 | self.cron.write() 39 | 40 | def delete_cron(self, cron_id): 41 | execute("DELETE FROM crontab where cron_id = ?;", (cron_id,)) 42 | for task in self.cron: 43 | if task.comment == str(cron_id): 44 | self.cron.remove(task) 45 | self.cron.write() 46 | 47 | def select_cron(self): 48 | tasks = execute("SELECT * FROM crontab;") 49 | result = [] 50 | for i in tasks: 51 | 52 | nxt = "" 53 | for task in self.cron: 54 | if task.comment == str(i[0]): 55 | nxt = task.schedule(date_from=datetime.now()).get_next() 56 | 57 | result.append({ 58 | "next": nxt, 59 | "cron_id": i[0], 60 | "cron_type": i[1], 61 | "command": i[2], 62 | "description": i[3] 63 | }) 64 | 65 | return result 66 | 67 | 68 | if __name__ == '__main__': 69 | from crontab import CronTab 70 | 71 | my_cron = CronTab(user=getpass.getuser()) 72 | for job in my_cron: 73 | print(job) 74 | -------------------------------------------------------------------------------- /model/docker_manager.py: -------------------------------------------------------------------------------- 1 | from subprocess import getstatusoutput, getoutput, Popen, PIPE 2 | 3 | 4 | class DockerManager: 5 | def __init__(self, container_id): 6 | self.container_id = container_id 7 | 8 | @staticmethod 9 | def get_container(): 10 | container_id_run = set(getoutput("docker ps | awk ' NR>1 {print $1} '").split('\n')) 11 | container_id_stop = set(getoutput("docker ps -a | awk ' NR>1 {print $1} '").split('\n')) - container_id_run 12 | container_run = [] 13 | container_stop = [] 14 | for i in container_id_run: 15 | info = getoutput("docker stats --no-stream " + i + " | awk 'NR>1 {print $0}'").split(' ') 16 | info = [x for x in info if x != ""] 17 | container_run.append({ 18 | "container_id": i, 19 | "name": info[1], 20 | "cpu": info[2], 21 | "mem": info[6] 22 | }) 23 | for i in container_id_stop: 24 | info = getoutput("docker stats --no-stream " + i + " | awk 'NR>1 {print $0}'").split(' ') 25 | info = [x for x in info if x != ""] 26 | container_stop.append({ 27 | "container_id": i, 28 | "name": info[1], 29 | "cpu": info[2], 30 | "mem": info[6] 31 | }) 32 | return { 33 | "container_run": container_run, 34 | "container_stop": container_stop 35 | } 36 | 37 | def stop_container(self): 38 | getoutput('docker stop {}'.format(self.container_id)) 39 | 40 | def restart_container(self): 41 | getoutput('docker restart {}'.format(self.container_id)) 42 | 43 | def delete_container(self): 44 | getoutput('docker container rm {}'.format(self.container_id)) 45 | 46 | 47 | if __name__ == '__main__': 48 | print(DockerManager.get_container()) 49 | -------------------------------------------------------------------------------- /model/file_manager.py: -------------------------------------------------------------------------------- 1 | import os 2 | from subprocess import getstatusoutput, getoutput, Popen, PIPE 3 | 4 | 5 | class FileManager: 6 | def __init__(self, path): 7 | self.path = path 8 | pass 9 | 10 | @staticmethod 11 | def _change_mode_format(mode): 12 | try: 13 | m = {"r": 4, "w": 2, "x": 1, "-": 0, "s": 0, "t": 0, "i": 0, "o": 0} 14 | return "".join([str(i) for i in [ 15 | sum([m[i] for i in mode[:3]]), 16 | sum([m[i] for i in mode[3: 6]]), 17 | sum([m[i] for i in mode[6:]]) 18 | ]]) 19 | except Exception as e: 20 | print(e) 21 | print(mode) 22 | 23 | def get_file_list(self): 24 | info = getoutput('ls -al {}'.format(self.path)).split('\n') 25 | file = [] 26 | for i in info[1:]: 27 | tmp = i.split(' ') 28 | tmp = [x for x in tmp if x != ""] 29 | 30 | if tmp[0][0] == 'd': 31 | file_type = 'dir' 32 | elif tmp[-1].endswith('html'): 33 | file_type = 'html' 34 | elif tmp[-1].endswith('png') or tmp[-1].endswith('jpg'): 35 | file_type = 'pic' 36 | else: 37 | file_type = "nonefile" 38 | 39 | file.append({ 40 | 'file_type': file_type, 41 | 'mode': FileManager._change_mode_format(tmp[0][1:]), 42 | 'owner': tmp[3], 43 | 'name': tmp[-1] 44 | }) 45 | return file 46 | 47 | def delete_file(self): 48 | print("rm -rf " + self.path) 49 | print(getoutput("rm -rf " + self.path)) 50 | 51 | def upload(self, file): 52 | file.save(os.path.join(self.path, file.filename)) 53 | 54 | 55 | if __name__ == '__main__': 56 | print(FileManager.delete_file(r'/home/chunibyo/Desktop/test')) 57 | -------------------------------------------------------------------------------- /model/ftp_manager.py: -------------------------------------------------------------------------------- 1 | from subprocess import getstatusoutput, getoutput, Popen, PIPE 2 | 3 | from model.file_manager import FileManager 4 | 5 | 6 | class FtpManager(FileManager): 7 | def __init__(self, path): 8 | self.root_path = "/srv/ftp" 9 | super().__init__(self.root_path + path) 10 | 11 | def get_file_list(self): 12 | return super().get_file_list() 13 | 14 | def delete_file(self): 15 | super().delete_file() 16 | 17 | def upload(self, file): 18 | super().upload(file) 19 | 20 | @staticmethod 21 | def install(): 22 | return getoutput('sh script/install_ftp.sh') 23 | 24 | 25 | if __name__ == '__main__': 26 | print(FtpManager.install().split('\n')) 27 | -------------------------------------------------------------------------------- /model/nginx_manager.py: -------------------------------------------------------------------------------- 1 | from subprocess import getoutput 2 | 3 | from model.file_manager import FileManager 4 | from model.sql_func import execute 5 | 6 | 7 | class NginxManger: 8 | def __init__(self, name): 9 | self.name = name 10 | 11 | def delete_file(self): 12 | getoutput('rm -rf /var/www/nginx/' + self.name) 13 | getoutput('rm -f /tmp/' + self.name + '.zip') 14 | getoutput('rm -f /etc/nginx/sites-enabled/' + self.name) 15 | getoutput('sudo nginx -s reload') 16 | execute('DELETE FROM nginx WHERE name = ?', (self.name,)) 17 | 18 | def upload(self, file, port): 19 | self.delete_file() 20 | FileManager('/tmp').upload(file) 21 | log = getoutput('sh script/create_website.sh {} {}'.format(self.name, port)) 22 | print(log) 23 | execute('INSERT INTO NGINX (name, port) values (?, ?)', (self.name, port)) 24 | 25 | @staticmethod 26 | def get_web_list(): 27 | return execute('SELECT * FROM nginx') 28 | 29 | 30 | if __name__ == '__main__': 31 | NginxManger("fff").upload('ff', 55) 32 | -------------------------------------------------------------------------------- /model/oss.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | 4 | import oss2 5 | import yaml 6 | import sys 7 | 8 | 9 | def backup(object_name, local_file): 10 | with open('config/oss.yaml', 'r') as f: 11 | oss = yaml.load(f, Loader=yaml.SafeLoader)['oss'] 12 | 13 | # 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。 14 | auth = oss2.Auth(oss['Auth']['AccessKeyId'], oss['Auth']['AccessKeySecret']) 15 | # Endpoint以杭州为例,其它Region请按实际情况填写。 16 | bucket = oss2.Bucket(auth, oss['Bucket']['EndPoint'], oss['Bucket']['BucketName']) 17 | 18 | # 上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。 19 | # 由本地文件路径加文件名包括后缀组成,例如/users/local/myfile.txt。 20 | bucket.put_object_from_file(object_name, local_file) 21 | 22 | 23 | if __name__ == '__main__': 24 | backup( 25 | sys.argv[1], 26 | sys.argv[2] 27 | ) 28 | -------------------------------------------------------------------------------- /model/process_manager.py: -------------------------------------------------------------------------------- 1 | import psutil 2 | from subprocess import getoutput 3 | 4 | 5 | class ProcessManger: 6 | def __init__(self, pid): 7 | self.pid = pid 8 | pass 9 | 10 | @staticmethod 11 | def get_processes(): 12 | process = [] 13 | for i in psutil.pids(): 14 | proc = psutil.Process(i) 15 | if proc.exe() not in process: 16 | process.append({ 17 | "pid": i, 18 | "proc_name": proc.name(), 19 | "mem": round(proc.cpu_percent(), 2), 20 | "cpu": round(proc.memory_percent(), 2), 21 | "status": proc.status() 22 | }) 23 | return process 24 | 25 | def kill_process(self): 26 | print("kill -9 " + self.pid) 27 | getoutput("kill -9 " + self.pid) 28 | 29 | def get_process(self): 30 | proc = psutil.Process(self.pid) 31 | return { 32 | "proc_name": proc.name(), 33 | "mem": round(proc.cpu_percent(), 2), 34 | "cpu": round(proc.memory_percent(), 2), 35 | "status": proc.status() 36 | } 37 | 38 | 39 | if __name__ == '__main__': 40 | print(ProcessManger.get_processes()) 41 | -------------------------------------------------------------------------------- /model/sql_func.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | 4 | def execute(*args, **kwargs): 5 | conn = sqlite3.connect('db/Linux.db') 6 | cursor = conn.cursor() 7 | cursor.execute(*args, **kwargs) 8 | result = cursor.fetchall() 9 | conn.commit() 10 | conn.close() 11 | 12 | return result 13 | 14 | 15 | def insert(*args, **kwargs): 16 | conn = sqlite3.connect('db/Linux.db') 17 | cursor = conn.cursor() 18 | cursor.execute(*args, **kwargs) 19 | result = cursor.lastrowid 20 | conn.commit() 21 | conn.close() 22 | 23 | return result 24 | 25 | 26 | # if __name__ == '__main__': 27 | # execute('INSERT INTO NGINX (name, port) values (?, ?)', ('a', 8500)) 28 | # a = execute('SELECT * FROM nginx WHERE name = ?', ("a",)) 29 | # execute('DELETE FROM nginx WHERE name = ?', ("a",)) 30 | # print(a) 31 | -------------------------------------------------------------------------------- /model/system_group.py: -------------------------------------------------------------------------------- 1 | from subprocess import getstatusoutput, getoutput, Popen, PIPE 2 | 3 | 4 | class SystemGroup: 5 | def __init__(self, name): 6 | self.name = name 7 | pass 8 | 9 | def add_user(self): 10 | getoutput('groupadd {0}'.format(self.name)) 11 | 12 | def delete_passwd(self): 13 | getoutput('groupdel {0}'.format(self.name)) 14 | -------------------------------------------------------------------------------- /model/system_info.py: -------------------------------------------------------------------------------- 1 | import os 2 | from subprocess import getstatusoutput, getoutput 3 | from time import sleep 4 | 5 | import psutil 6 | 7 | from model.process_manager import ProcessManger 8 | 9 | 10 | class SystemWatch: 11 | def __init__(self): 12 | pass 13 | 14 | @staticmethod 15 | def get_cpu_info(): 16 | cpu = { 17 | 'model_name': getoutput('cat /proc/cpuinfo | grep "model name" | uniq').split(':')[1][1:], 18 | 'cpu_num': int(getoutput('cat /proc/cpuinfo | grep "physical id" | sort | uniq | wc -l')), 19 | 'core_num': int(getoutput('cat /proc/cpuinfo | grep "cpu cores" | wc -l')), 20 | 'processor_num': int(getoutput('cat /proc/cpuinfo | grep "processor" | wc -l')) 21 | } 22 | return cpu 23 | 24 | @staticmethod 25 | def get_mem_info(): 26 | with open('/proc/meminfo') as f: 27 | mem = {} 28 | cnt = 0 29 | for line in f: 30 | tmp = line.split(' ') 31 | # GB 表示 32 | mem[tmp[0][:-1]] = round(int(tmp[len(tmp) - 2]) / 2 ** 20, 2) 33 | 34 | cnt += 1 35 | if cnt > 6: 36 | break 37 | 38 | mem['Used'] = round(mem['MemTotal'] - mem['MemFree'] - mem['Buffers'] - mem['Cached'], 2) 39 | return mem 40 | 41 | @staticmethod 42 | def get_disk_info(): 43 | return { 44 | 'Used': round(psutil.disk_usage('/').used / 2 ** 30), 45 | 'Total': round(psutil.disk_usage('/').total / 2 ** 30) 46 | } 47 | 48 | @staticmethod 49 | def get_io_info(): 50 | pre_net_io_sent = psutil.net_io_counters().bytes_sent 51 | pre_net_io_recv = psutil.net_io_counters().bytes_recv 52 | pre_io_sent = psutil.disk_io_counters().write_bytes 53 | pre_io_recv = psutil.disk_io_counters().read_bytes 54 | sleep(1) 55 | cur_net_io_sent = psutil.net_io_counters().bytes_sent 56 | cur_net_io_recv = psutil.net_io_counters().bytes_recv 57 | cur_io_sent = psutil.disk_io_counters().write_bytes 58 | cur_io_recv = psutil.disk_io_counters().read_bytes 59 | # print(pre_net_io_recv, cur_io_recv) 60 | return { 61 | 'net_io_sent': round((cur_net_io_sent - pre_net_io_sent)), 62 | 'net_io_recv': round((cur_net_io_recv - pre_net_io_recv)), 63 | 'io_sent': round((cur_io_sent - pre_io_sent)), 64 | 'io_recv': round((cur_io_recv - pre_io_recv)) 65 | } 66 | 67 | @staticmethod 68 | def get_netstat_info(): 69 | # laddr means local address and raddr means remote address of the socket. 70 | result = [] 71 | for i in psutil.net_connections(): 72 | if i.status == 'ESTABLISHED': 73 | result.append(ProcessManger(i.pid).get_process()) 74 | result[-1]["laddr_ip"] = i.laddr.ip 75 | result[-1]["laddr_port"] = i.laddr.port 76 | result[-1]["raddr_ip"] = i.raddr.ip 77 | result[-1]["raddr_port"] = i.raddr.port 78 | 79 | return result 80 | 81 | 82 | if __name__ == '__main__': 83 | SystemWatch.get_netstat_info() 84 | -------------------------------------------------------------------------------- /model/system_user.py: -------------------------------------------------------------------------------- 1 | from subprocess import getstatusoutput, getoutput, Popen, PIPE 2 | 3 | 4 | class SystemUser: 5 | def __init__(self, name, passwd=None): 6 | self.name = name 7 | self.passwd = passwd 8 | pass 9 | 10 | def add_user(self): 11 | getoutput('useradd -m {0}'.format(self.name)) 12 | 13 | def add_passwd(self): 14 | Popen(['passwd', self.name], stdin=PIPE) \ 15 | .communicate( 16 | '{0}\n{1}' 17 | .format(self.passwd, self.passwd) 18 | .encode() 19 | ) 20 | 21 | def delete_passwd(self): 22 | getoutput('userdel -r {0}'.format(self.name)) 23 | 24 | 25 | if __name__ == '__main__': 26 | SystemUser('chunibyo', '123\n123').add_passwd() 27 | -------------------------------------------------------------------------------- /model/ufw_manager.py: -------------------------------------------------------------------------------- 1 | import os 2 | from subprocess import getoutput 3 | 4 | try: 5 | from .sql_func import insert, execute 6 | except Exception as e: 7 | print("===========") 8 | print(e) 9 | print("===========") 10 | from sql_func import insert, execute 11 | 12 | 13 | class PortManager: 14 | def __init__(self, port=80, protocol='tcp'): 15 | self.port = port 16 | self.protocol = protocol 17 | 18 | def add_allow_port(self, description): 19 | execute( 20 | "INSERT INTO ufw_port (port, protocol, description) VALUES(?, ?, ?)", 21 | (self.port, self.protocol, description) 22 | ) 23 | os.system("sudo ufw allow {}/{}".format(self.port, self.protocol)) 24 | 25 | @staticmethod 26 | def get_allow_port(): 27 | ports = execute("SELECT * FROM ufw_port;") 28 | result = [] 29 | for i in ports: 30 | result.append({ 31 | "rule_id": i[0], 32 | "port": i[1], 33 | "protocol": i[2], 34 | "description": i[3] 35 | }) 36 | return result 37 | 38 | def delete_allow_port(self): 39 | execute( 40 | "DELETE FROM ufw_port WHERE port = ? AND protocol = ?;", 41 | (self.port, self.protocol) 42 | ) 43 | os.system("sudo ufw delete allow {}/{}".format(self.port, self.protocol)) 44 | pass 45 | 46 | 47 | class IPManager: 48 | def __init__(self, ip='127.0.0.1', protocol='tcp'): 49 | self.ip = ip 50 | self.protocol = protocol 51 | 52 | def add_deny_ip(self, description): 53 | execute( 54 | "INSERT INTO ufw_ip (ip, protocol, description) VALUES(?, ?, ?)", 55 | (self.ip, self.protocol, description) 56 | ) 57 | os.system("sudo ufw deny from {} proto {}".format(self.ip, self.protocol)) 58 | 59 | @staticmethod 60 | def get_deny_ip(): 61 | ports = execute("SELECT * FROM ufw_ip;") 62 | result = [] 63 | for i in ports: 64 | result.append({ 65 | "rule_id": i[0], 66 | "ip": i[1], 67 | "protocol": i[2], 68 | "description": i[3] 69 | }) 70 | return result 71 | 72 | def delete_deny_ip(self): 73 | execute( 74 | "DELETE FROM ufw_ip WHERE ip = ? AND protocol = ?;", 75 | (self.ip, self.protocol) 76 | ) 77 | os.system("sudo ufw delete deny from {} proto {}".format(self.ip, self.protocol)) 78 | 79 | 80 | if __name__ == '__main__': 81 | # IPManager('192.168.1.1', 'tcp').add_deny_ip('test') 82 | # print(IPManager.get_deny_ip()) 83 | IPManager('192.168.1.1', 'tcp').delete_deny_ip() 84 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask~=1.1.2 2 | flask-cors 3 | Flask-SocketIO 4 | psutil~=5.7.0 5 | PyYAML~=5.3.1 6 | python-crontab~=2.5.1 7 | croniter 8 | oss2~=2.11.0 -------------------------------------------------------------------------------- /route/__init__.py: -------------------------------------------------------------------------------- 1 | from .crontab_manager import * 2 | from .dns import * 3 | from .docker_manager import * 4 | from .file_manager import * 5 | from .ftp_manager import * 6 | from .login import * 7 | from .nginx_manager import * 8 | from .parameter import * 9 | from .process_manager import * 10 | from .system_info import * 11 | from .system_user import * 12 | from .ufw_manager import * 13 | -------------------------------------------------------------------------------- /route/crontab_manager.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from flask import request, jsonify 4 | 5 | from main import app 6 | from model.crontab_manager import CrontabManager 7 | 8 | 9 | @app.route('/api/crontab', methods=['GET', 'POST', 'DELETE']) 10 | def handle_crontab(): 11 | if request.method == "GET": 12 | return jsonify(CrontabManager().select_cron()) 13 | elif request.method == "POST": 14 | CrontabManager(request.form['cron_type']).insert_cron( 15 | request.form['command'], request.form['description'] 16 | ) 17 | return jsonify({"status": "OK"}) 18 | elif request.method == 'DELETE': 19 | CrontabManager().delete_cron(request.form['cron_id']) 20 | return jsonify({"status": "OK"}) 21 | 22 | 23 | @app.route('/api/cwd', methods=['GET']) 24 | def handle_cwd(): 25 | return os.getcwd() 26 | -------------------------------------------------------------------------------- /route/dns.py: -------------------------------------------------------------------------------- 1 | from flask import request 2 | 3 | from main import app 4 | 5 | 6 | @app.route('/api/dns', methods=['GET', 'POST']) 7 | def handle_dns(): 8 | response = {} 9 | 10 | if request.method == 'GET': 11 | cnt = 0 12 | with open('/etc/resolvconf/resolv.conf.d/base') as f: 13 | for line in f: 14 | cnt += 1 15 | response['server_' + str(cnt)] = line.split(' ')[-1][:-1] 16 | 17 | elif request.method == 'POST': 18 | print(request.data) 19 | with open('/etc/resolvconf/resolv.conf.d/base', 'w') as f: 20 | f.writelines([ 21 | 'nameserver\t' + request.json['server_1'] + '\n', 22 | 'nameserver\t' + request.json['server_2'] + '\n' 23 | ]) 24 | response = { 25 | 'status': 'OK' 26 | } 27 | return response 28 | -------------------------------------------------------------------------------- /route/docker_manager.py: -------------------------------------------------------------------------------- 1 | from flask import request, jsonify 2 | 3 | from main import app 4 | from model.docker_manager import DockerManager 5 | 6 | 7 | @app.route('/api/docker', methods=['GET', 'PUT']) 8 | def handle_docker(): 9 | if request.method == "GET": 10 | return jsonify(DockerManager.get_container()) 11 | elif request.method == "PUT": 12 | if request.form["operation"] == "stop": 13 | DockerManager(request.form["container_id"]).stop_container() 14 | return jsonify(DockerManager.get_container()) 15 | elif request.form["operation"] == "start": 16 | DockerManager(request.form["container_id"]).restart_container() 17 | return jsonify(DockerManager.get_container()) 18 | -------------------------------------------------------------------------------- /route/file_manager.py: -------------------------------------------------------------------------------- 1 | import flask 2 | from flask import request, jsonify, send_from_directory 3 | 4 | from main import app 5 | from model.file_manager import FileManager 6 | 7 | 8 | @app.route('/api/file_list', methods=['GET']) 9 | def handle_file_list(): 10 | # 获取目录信息 11 | if request.method == 'GET': 12 | return jsonify({ 13 | "file": FileManager(request.args.get('path')).get_file_list() 14 | }) 15 | 16 | 17 | @app.route('/api/file', methods=['GET', 'DELETE', 'PATCH']) 18 | def handel_file(): 19 | if request.method == 'GET': 20 | # 下载文件 21 | print(request.values.get('path')) 22 | return flask.send_file(request.values.get('path'), as_attachment=True) 23 | elif request.method == 'DELETE': 24 | # 删除文件 25 | FileManager(request.json['path']).delete_file() 26 | return jsonify({"status": "OK"}) 27 | elif request.method == 'PATCH': 28 | print(request.form['path']) 29 | FileManager(request.form['path']).upload(request.files.get('file')) 30 | return jsonify({"status": "OK"}) 31 | -------------------------------------------------------------------------------- /route/ftp_manager.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import flask 4 | from flask import request, jsonify 5 | 6 | from main import app 7 | from model.ftp_manager import FtpManager 8 | 9 | 10 | @app.route('/api/ftp_list', methods=['GET']) 11 | def handle_ftp_list(): 12 | # 获取目录信息 13 | if request.method == 'GET': 14 | return jsonify({ 15 | "file": FtpManager(request.args.get('path')).get_file_list() 16 | }) 17 | 18 | 19 | @app.route('/api/ftp', methods=['GET', 'DELETE', 'PATCH']) 20 | def handle_ftp(): 21 | if request.method == 'GET': 22 | # 下载文件 23 | return flask.send_file('/srv/ftp' + request.values.get('path'), as_attachment=True) 24 | elif request.method == 'DELETE': 25 | # 删除文件 26 | FtpManager(request.json['path']).delete_file() 27 | return jsonify({"status": "OK"}) 28 | elif request.method == 'PATCH': 29 | FtpManager(request.form['path']).upload(request.files.get('file')) 30 | return jsonify({"status": "OK"}) 31 | -------------------------------------------------------------------------------- /route/login.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | import yaml 4 | from flask import request, make_response, redirect, url_for 5 | 6 | from main import app 7 | 8 | 9 | @app.route('/api/login', methods=['POST']) 10 | def log_in(): 11 | with open('config/config.yaml', 'r') as f: 12 | user = yaml.load(f, Loader=yaml.SafeLoader)['user'] 13 | if request.json['name'] == user['name'] and request.json['passwd'] == user['passwd']: 14 | response = { 15 | "status": "OK" 16 | } 17 | else: 18 | response = { 19 | "status": "WRONG" 20 | } 21 | resp = make_response(response) 22 | resp.set_cookie('login', 'True') 23 | return resp 24 | 25 | 26 | @app.route('/api/logout', methods=['POST']) 27 | def log_out(): 28 | if 'login' not in request.cookies: 29 | return { 30 | "status": "WRONG" 31 | } 32 | 33 | response = { 34 | "status": "OK" 35 | } 36 | resp = make_response(response) 37 | resp.delete_cookie('login') 38 | return resp 39 | -------------------------------------------------------------------------------- /route/nginx_manager.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import flask 4 | from flask import request, jsonify 5 | 6 | from main import app 7 | from model.nginx_manager import NginxManger 8 | 9 | 10 | @app.route('/api/nginx', methods=['GET', 'DELETE', 'PATCH']) 11 | def handle_nginx(): 12 | if request.method == 'GET': 13 | # 获取现有的网站 14 | web_list = NginxManger.get_web_list() 15 | result = [] 16 | for i in web_list: 17 | result.append({"name": i[0], "port": i[1]}) 18 | return jsonify({ 19 | "result": result 20 | }) 21 | elif request.method == 'DELETE': 22 | # 删除文件 23 | NginxManger(request.form['name']).delete_file() 24 | return jsonify({"status": "OK"}) 25 | elif request.method == 'PATCH': 26 | file = request.files.get('file') 27 | NginxManger(file.filename.split('.')[0]).upload(file, request.form['port']) 28 | return jsonify({"status": "OK"}) 29 | -------------------------------------------------------------------------------- /route/parameter.py: -------------------------------------------------------------------------------- 1 | import flask 2 | from flask import request, jsonify, send_from_directory 3 | from main import app 4 | 5 | 6 | @app.route('/api/url', methods=['GET']) 7 | def get_url(): 8 | return request.args.get("KEY") 9 | 10 | 11 | @app.route('/api/textplain', methods=['GET']) 12 | def get_textplain(): 13 | return request.data 14 | 15 | 16 | @app.route('/api/form', methods=['GET']) 17 | def get_form(): 18 | return request.form["KEY"] 19 | 20 | 21 | @app.route('/api/json', methods=['GET']) 22 | def get_json(): 23 | return request.json["KEY"] 24 | -------------------------------------------------------------------------------- /route/process_manager.py: -------------------------------------------------------------------------------- 1 | import flask 2 | from flask import request, jsonify, send_from_directory 3 | from main import app 4 | from model.process_manager import ProcessManger 5 | 6 | 7 | @app.route('/api/process', methods=['GET', 'DELETE']) 8 | def get_process(): 9 | if request.method == 'GET': 10 | return jsonify( 11 | sorted( 12 | ProcessManger.get_processes(), 13 | key=lambda item: item['cpu'], 14 | reverse=True 15 | )[:20] 16 | ) 17 | elif request.method == 'DELETE': 18 | try: 19 | ProcessManger(request.form['pid']).kill_process() 20 | except Exception as e: 21 | print(e) 22 | return { 23 | "status": "OK" 24 | } 25 | -------------------------------------------------------------------------------- /route/system_info.py: -------------------------------------------------------------------------------- 1 | from flask import jsonify 2 | 3 | from main import app 4 | from model.system_info import SystemWatch 5 | 6 | 7 | @app.route('/api/sysinfo', methods=['GET']) 8 | def get_sys_info(): 9 | return jsonify({ 10 | 'cpu_info': SystemWatch.get_cpu_info(), 11 | 'mem_info': SystemWatch.get_mem_info(), 12 | 'disk_info': SystemWatch.get_disk_info(), 13 | 'io_info': SystemWatch.get_io_info() 14 | }) 15 | 16 | 17 | @app.route('/api/netstat', methods=['GET']) 18 | def get_netstat(): 19 | return jsonify(SystemWatch.get_netstat_info()[:20]) 20 | -------------------------------------------------------------------------------- /route/system_user.py: -------------------------------------------------------------------------------- 1 | from flask import request 2 | 3 | from main import app 4 | from model.system_user import SystemUser 5 | 6 | 7 | @app.route('/api/user', methods=['GET', 'POST']) 8 | def handle_user(): 9 | if request.method == 'GET': 10 | pass 11 | elif request.method == 'POST': 12 | operation = request.json['operation'] 13 | if operation == 'add': 14 | user = SystemUser(request.json['name'], request.json['passwd']) 15 | user.add_user() 16 | user.add_passwd() 17 | elif operation == 'delete': 18 | user = SystemUser(request.json['name']) 19 | user.delete_passwd() 20 | return { 21 | "status": "OK" 22 | } 23 | -------------------------------------------------------------------------------- /route/ufw_manager.py: -------------------------------------------------------------------------------- 1 | from flask import request, jsonify 2 | 3 | from main import app 4 | from model.ufw_manager import PortManager, IPManager 5 | 6 | 7 | @app.route('/api/ufw/port', methods=['GET', 'POST', 'DELETE']) 8 | def handle_ufw_port(): 9 | if request.method == 'GET': 10 | return jsonify(PortManager.get_allow_port()) 11 | elif request.method == 'DELETE': 12 | PortManager(request.form['port'], request.form['protocol']).delete_allow_port() 13 | return jsonify({"status": "OK"}) 14 | elif request.method == 'POST': 15 | PortManager(request.form['port'], request.form['protocol']).add_allow_port(request.form['description']) 16 | return jsonify({"status": "OK"}) 17 | 18 | 19 | @app.route('/api/ufw/ip', methods=['GET', 'POST', 'DELETE']) 20 | def handle_ufw_ip(): 21 | if request.method == 'GET': 22 | return jsonify(IPManager.get_deny_ip()) 23 | elif request.method == 'DELETE': 24 | IPManager(request.form['ip'], request.form['protocol']).delete_deny_ip() 25 | return jsonify({"status": "OK"}) 26 | elif request.method == 'POST': 27 | IPManager(request.form['ip'], request.form['protocol']).add_deny_ip(request.form['description']) 28 | return jsonify({"status": "OK"}) 29 | -------------------------------------------------------------------------------- /script/create_website.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 参数1 : 文件名 4 | # 参数2 : 端口号 5 | 6 | # 1. 创建网站托管文件夹 7 | if [ ! -d "/var/www/nginx/"$1 ]; then 8 | sudo mkdir /var/www/nginx/$1 9 | fi 10 | 11 | # 2. 创建配置文件 12 | sudo echo ' 13 | server { 14 | listen '$2' default_server; 15 | listen [::]:'$2' default_server; 16 | 17 | server_name _; 18 | 19 | root /var/www/nginx/'$1'; 20 | index index.html index.htm index.nginx-debian.html; 21 | 22 | location / { 23 | try_files $uri $uri/ =404; 24 | } 25 | }' > /etc/nginx/sites-enabled/$1 26 | 27 | 28 | # 3. 解压网站压缩包 29 | unzip /tmp/$1.zip -d /var/www/nginx 30 | 31 | # 4. 重启服务 32 | sudo nginx -s reload -------------------------------------------------------------------------------- /script/install_crontab.sh: -------------------------------------------------------------------------------- 1 | sudo apt update && sudo apt -y upgrade 2 | sudo apt install -y ntpdate 3 | -------------------------------------------------------------------------------- /script/install_ftp.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # 1. 安装vsftp 4 | sudo apt update && sudo apt -y upgrade 5 | sudo apt install -y vsftpd 6 | 7 | # 2. 创建可以用于上传的文件夹 8 | if [ ! -d "/srv/ftp/pub" ]; then 9 | sudo mkdir -p /srv/ftp/pub 10 | fi 11 | sudo chown ftp:ftp /srv/ftp/pub 12 | sudo echo "vsftpd test file" > /srv/ftp/vsftpd_test.txt 13 | 14 | # 3. 备份配置文件 15 | file=/etc/vsftpd.conf 16 | if [ -f "$file" ]; then 17 | sudo cp $file $file.org 18 | fi 19 | 20 | # 4. 输入新的配置文件 21 | sudo echo " 22 | listen=NO 23 | listen_ipv6=YES 24 | anonymous_enable=YES 25 | no_anon_password=YES 26 | local_enable=YES 27 | write_enable=YES 28 | anon_upload_enable=YES 29 | anon_mkdir_write_enable=YES 30 | dirmessage_enable=YES 31 | use_localtime=YES 32 | xferlog_enable=YES 33 | connect_from_port_20=YES 34 | secure_chroot_dir=/var/run/vsftpd/empty 35 | pam_service_name=vsftpd 36 | rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem 37 | rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key 38 | ssl_enable=NO 39 | utf8_filesystem=YES 40 | pasv_min_port=8500 41 | pasv_max_port=8600 42 | " > /etc/vsftpd.conf 43 | 44 | 45 | systemctl restart vsftpd 46 | -------------------------------------------------------------------------------- /script/install_nginx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 1. 安装nginx 4 | sudo apt update && sudo apt -y upgrade 5 | sudo apt install -y nginx 6 | sudo apt install -y unzip 7 | 8 | # 2. 创建网站存储目录 9 | sudo mkdir -p /var/www/nginx -------------------------------------------------------------------------------- /script/install_ufw.sh: -------------------------------------------------------------------------------- 1 | sudo apt install ufw 2 | 3 | ufw allow ssh 4 | ufw allow 8888 5 | echo y | sudo ufw enable -------------------------------------------------------------------------------- /static/css/file.css: -------------------------------------------------------------------------------- 1 | /*table*/ 2 | /*.site-table tbody tr td {text-align: center;}*/ 3 | /*.site-table tbody tr td .layui-btn+.layui-btn{margin-left: 0px;}*/ 4 | /*.admin-table-page {position: fixed;z-index: 19940201;bottom: 0;width: 100%;background-color: #eee;border-bottom: 1px solid #ddd;left: 0px;}*/ 5 | /*.admin-table-page .page{padding-left:20px;}*/ 6 | /*.admin-table-page .page .layui-laypage {margin: 6px 0 0 0;}*/ 7 | /*.table-hover tbody tr:hover{ background-color: #EEEEEE; }*/ 8 | 9 | 10 | .table td { 11 | vertical-align: middle; 12 | !important; 13 | } -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/static/favicon.ico -------------------------------------------------------------------------------- /static/html/crontab.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 石之海 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 33 | 34 | 35 | 36 | 37 |
38 | 39 | 40 | 41 | 129 | 130 | 131 | 132 |
133 | 134 |
135 | 136 | 137 |
138 |
139 | 147 |
148 |
149 | 150 | 151 |
152 |
153 |

添加计划任务

154 |
155 |
156 |
157 | 158 |
159 | 160 |
161 |
162 | 163 |
164 |
165 |
166 | 167 |
168 | 169 |
170 |
171 | 176 |
177 |
178 |
179 | 180 |
181 | 182 |
183 |
184 | 191 |
192 |
193 |
194 | 195 | 203 | 204 | 212 | 213 |
214 | 215 |
216 |
217 | 218 |
219 |
220 |
221 | 222 |
223 |
224 | 225 |
226 |
227 |
228 | 229 |
230 |
231 |
    232 |
  • 当添加完备份任务,应该手动运行一次,并检查备份包是否完整
  • 233 |
  • 磁盘容量不够、数据库密码错误、网络不稳定等原因,可能导致数据备份不完整
  • 234 |
  • 备份站点和目录时支持文件或目录排除,请将需要排除功能的插件升级到最新版,如:阿里云OSS等
  • 235 |
236 |
237 | 238 |
239 |
240 | 241 |
242 |
243 |

任务列表

244 |
245 |
246 |
247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 |
序号名称命令执行周期下一次执行时间操作
271 |
272 |
273 |
274 | 275 |
276 | 277 | 278 |
279 | 280 |
281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | -------------------------------------------------------------------------------- /static/html/docker.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 石之海 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | 120 | 121 | 122 | 123 |
124 | 125 |
126 | 127 | 128 |
129 |
130 | 138 |
139 |
140 | 141 | 142 | 143 |
144 | 145 |
146 |
147 |
148 |

运行中

149 |
150 |
151 |
152 |
153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 |
容器名CPU%MEM%操作
173 |
174 |
175 |
176 |
177 |
178 | 179 |
180 | 181 | 182 | 183 |
184 | 185 |
186 |
187 |
188 |

停止中

189 |
190 |
191 |
192 |
193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 |
容器名CPU%MEM%操作
213 |
214 |
215 |
216 |
217 |
218 | 219 |
220 | 221 | 222 |
223 | 224 | 225 |
226 | 227 | 228 |
229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /static/html/file.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 石之海 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 32 | 33 | 121 | 122 | 123 | 124 |
125 | 126 |
127 | 128 | 129 |
130 |
131 | 139 |
140 |
141 | 145 |
146 |
147 |
148 | 149 | 150 |
151 |
152 |
153 | 154 | 155 |
156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 |
文件名权限所有者操作
167 |
168 | 169 |
    170 |
  • «
  • 171 |
  • 1
  • 172 |
  • »
  • 173 |
174 | 175 |
176 | 177 | 178 |
179 | 180 | 181 |
182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /static/html/ftp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 石之海 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 32 | 33 | 121 | 122 | 123 | 124 |
125 | 126 |
127 | 128 | 129 |
130 |
131 | 139 |
140 |
141 | 145 |
146 |
147 |
148 | 149 | 150 |
151 |
152 |
153 | 154 | 155 |
156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 |
文件名权限所有者操作
167 |
168 | 169 |
    170 |
  • «
  • 171 |
  • 1
  • 172 |
  • »
  • 173 |
174 | 175 |
176 | 177 | 178 |
179 | 180 | 181 |
182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /static/html/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 石之海 11 | 12 | 28 | 29 | 30 | 31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |

登录

40 |
41 |
42 |
43 |
44 | 45 | 47 |
48 |
49 | 50 | 52 |
53 |
54 | 55 | 登录
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /static/html/process.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 石之海 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | 120 | 121 | 122 | 123 |
124 | 125 |
126 | 127 | 128 |
129 |
130 | 138 |
139 |
140 | 141 | 142 |
143 |
144 | 145 |
146 |
147 |
148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 |
进程名%CPU%MEM状态        
170 |
171 |
172 |
173 | 174 | 175 |
176 | 177 |
178 | 179 |
180 |
181 |
182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 |
进程名%CPU%MEM本地地址本地端口远端地址远端端口
197 |
198 |
199 |
200 | 201 | 202 |
203 |
204 | 205 |
206 | 207 | 208 |
209 | 210 | 211 |
212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | -------------------------------------------------------------------------------- /static/html/ufw.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 石之海 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 33 | 34 | 35 | 36 | 37 |
38 | 39 | 40 | 41 | 129 | 130 | 131 | 132 |
133 | 134 |
135 | 136 | 137 |
138 |
139 | 147 |
148 |
149 | 150 | 151 |
152 |
153 |

154 | 155 | 放行端口 156 | 162 | 163 |

164 |
165 | 166 |
167 |
168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 |
序号放行端口协议说明操作
182 |
183 |
184 |
185 | 186 |
187 |
188 |

189 | 190 | 放行端口 191 | 197 | 198 |

199 |
200 |
201 |
202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 |
序号拒绝地址协议说明操作
215 |
216 |
217 |
218 | 219 |
220 | 221 | 222 |
223 | 224 |
225 | 226 | 227 | 228 | 276 | 277 | 278 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | -------------------------------------------------------------------------------- /static/icon/container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/static/icon/container.png -------------------------------------------------------------------------------- /static/icon/dir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/static/icon/dir.png -------------------------------------------------------------------------------- /static/icon/html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/static/icon/html.png -------------------------------------------------------------------------------- /static/icon/nonefile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/static/icon/nonefile.png -------------------------------------------------------------------------------- /static/icon/pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/static/icon/pic.png -------------------------------------------------------------------------------- /static/image/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/static/image/bg.jpg -------------------------------------------------------------------------------- /static/js/crontab.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | let cwd = "" 4 | 5 | let show_crontab = function (crontab_array) { 6 | let html = "" 7 | let $dummy = $("#cron-list") 8 | $dummy.empty() 9 | crontab_array.forEach(function (item) { 10 | html += '' + 11 | '' + 12 | ' ' + item['cron_id'] + '' + 13 | ' ' + item['description'] + '' + 14 | ' ' + item['command'] + '' + 15 | ' ' + item['cron_type'] + '' + 16 | ' ' + item['next'] + '' + 17 | ' ' + 18 | ' ' + 19 | ' ' + 20 | '' 21 | }) 22 | $dummy.append(html) 23 | 24 | $("button.btn-danger").click(function () { 25 | delete_crontab( 26 | $(this).parent().parent().attr("cron_id") 27 | ) 28 | }) 29 | } 30 | 31 | let get_crontab = function () { 32 | let settings = { 33 | "url": "/api/crontab", 34 | "method": "GET", 35 | "timeout": 0, 36 | }; 37 | 38 | $.ajax(settings).done(function (response) { 39 | show_crontab(response) 40 | }); 41 | }; 42 | 43 | let get_cwd = function () { 44 | let settings = { 45 | "url": "/api/cwd", 46 | "method": "GET", 47 | "timeout": 0, 48 | }; 49 | 50 | $.ajax(settings).done(function (response) { 51 | cwd = response.toString() 52 | }); 53 | } 54 | 55 | let post_crontab = function () { 56 | let settings = { 57 | "url": "/api/crontab", 58 | "method": "POST", 59 | "timeout": 0, 60 | "headers": { 61 | "Content-Type": "application/x-www-form-urlencoded" 62 | }, 63 | "data": { 64 | "cron_type": $("#cron-type").val(), 65 | "command": $("#command").val(), 66 | "description": $("#task-name").val() 67 | } 68 | }; 69 | 70 | $.ajax(settings).done(function (response) { 71 | $("#form")[0].reset() 72 | get_crontab() 73 | }); 74 | } 75 | 76 | let delete_crontab = function (cron_id) { 77 | let settings = { 78 | "url": "/api/crontab", 79 | "method": "DELETE", 80 | "timeout": 0, 81 | "headers": { 82 | "Content-Type": "application/x-www-form-urlencoded" 83 | }, 84 | "data": {"cron_id": cron_id} 85 | }; 86 | 87 | $.ajax(settings).done(function (response) { 88 | get_crontab() 89 | }); 90 | } 91 | 92 | $("button.btn-default").click(function () { 93 | post_crontab() 94 | }) 95 | 96 | $("#task_type").change(function () { 97 | 98 | let $selected = $("#task_type").val() 99 | if ($selected === "1") { 100 | $(".oss").hide() 101 | $("#task-name").val('') 102 | $('#cron-type option[value="minute"]').attr("selected", "selected") 103 | $("#command").val("") 104 | } else if ($selected === "2") { 105 | $("#command").val("") 106 | $(".oss").show() 107 | } else if ($selected === "3") { 108 | $(".oss").hide() 109 | $("#task-name").val('同步时间') 110 | $('#cron-type option[value="month"]').attr("selected", "selected") 111 | $("#command").val("sudo ntpdate ntp.aliyun.com > /tmp/ntpdate.log") 112 | } 113 | }) 114 | 115 | $(".oss").change(function () { 116 | $("#command").val("" + 117 | "cd " + cwd + " && venv/bin/python " + "model/oss.py " + $("#oss-object-name").val() + " " + $("#oss-local-file").val() 118 | ) 119 | }) 120 | 121 | get_cwd() 122 | get_crontab() 123 | }) 124 | -------------------------------------------------------------------------------- /static/js/docker.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | let stop_container = function (container_id) { 4 | let settings = { 5 | "url": "/api/docker", 6 | "method": "PUT", 7 | "timeout": 0, 8 | "headers": {"Content-Type": "application/x-www-form-urlencoded"}, 9 | "data": { 10 | "operation": "stop", 11 | "container_id": container_id 12 | } 13 | }; 14 | 15 | $.ajax(settings).done(function (response) { 16 | show_container(response["container_run"], response["container_stop"]) 17 | }); 18 | } 19 | 20 | let start_container = function (container_id) { 21 | let settings = { 22 | "url": "/api/docker", 23 | "method": "PUT", 24 | "timeout": 0, 25 | "headers": {"Content-Type": "application/x-www-form-urlencoded"}, 26 | "data": { 27 | "operation": "start", 28 | "container_id": container_id 29 | } 30 | }; 31 | 32 | $.ajax(settings).done(function (response) { 33 | show_container(response["container_run"], response["container_stop"]) 34 | }); 35 | } 36 | 37 | let show_container = function (run_array, stop_array) { 38 | let html = ""; 39 | run_array.forEach(function (item) { 40 | html = html + '' + 41 | '' + 42 | ' ' + item['name'] + '' + 43 | ' ' + item['cpu'] + '' + 44 | ' ' + item['mem'] + '' + 45 | ' ' + 46 | ' ' + 47 | ' ' + 48 | '' 49 | }) 50 | $("#run-list").empty().append(html); 51 | 52 | html = "" 53 | stop_array.forEach(function (item) { 54 | html = html + '' + 55 | '' + 56 | ' ' + item['name'] + '' + 57 | ' ' + item['cpu'] + '' + 58 | ' ' + item['mem'] + '' + 59 | ' ' + 60 | ' ' + 61 | ' ' + 62 | '' 63 | }) 64 | $("#stop-list").empty().append(html); 65 | 66 | // 停止事件 67 | $("td > .btn-danger").click(function () { 68 | stop_container($(this).parent().parent().attr("container_id")) 69 | }) 70 | 71 | // 启动事件 72 | $("td > .btn-info").click(function () { 73 | start_container($(this).parent().parent().attr("container_id")) 74 | }) 75 | } 76 | 77 | let get_container = function () { 78 | let settings = { 79 | "url": "/api/docker", 80 | "method": "GET", 81 | "timeout": 0, 82 | }; 83 | 84 | $.ajax(settings).done(function (response) { 85 | show_container(response["container_run"], response["container_stop"]) 86 | }); 87 | } 88 | 89 | get_container(); 90 | 91 | }) -------------------------------------------------------------------------------- /static/js/file.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | let path = []; 4 | let page_index = 1; 5 | let list_num = 15; 6 | let file_list; 7 | 8 | let array2str = function (path_array) { 9 | let path_str = "/" 10 | path_array.forEach(function (item) { 11 | path_str = path_str + item + "/" 12 | }) 13 | return path_str; 14 | } 15 | 16 | let get_file_list = function (_path, change = true) { 17 | let settings = { 18 | "async": true, 19 | "crossDomain": true, 20 | "url": "/api/file_list?path=" + _path, 21 | "method": "GET" 22 | }; 23 | 24 | $.ajax(settings).done(function (response) { 25 | if (typeof (response) === "string") { 26 | response = JSON.parse(response) 27 | } 28 | 29 | if (change) { 30 | page_index = 1; 31 | $("#page-index").html('' + page_index + ''); 32 | } 33 | file_list = response["file"]; 34 | get_file_list_by_index(page_index - 1); 35 | }); 36 | } 37 | 38 | let get_file_list_by_index = function (index) { 39 | let html = ""; 40 | for (let i = index * list_num; i < Math.min((index + 1) * list_num, file_list.length); ++i) { 41 | html = html + '' + 42 | '' + 43 | ' ' + 44 | '       ' + 45 | ' ' + 46 | '    ' + file_list[i]["name"] + 47 | ' ' + 48 | ' ' + file_list[i]["mode"] + '' + 49 | ' ' + file_list[i]["owner"] + '' + 50 | ' ' + 51 | '
' + 52 | ' 下载' + 53 | ' ' + 54 | // ' ' + 55 | '
' + 56 | ' ' + 57 | '' 58 | } 59 | $("#file-list").empty().append(html); 60 | 61 | // 文件夹点击事件 62 | $("td.name").click(function () { 63 | if ($(this).attr("file_type") === "dir") { 64 | if ($(this).attr("file_name") === ".") { 65 | 66 | } else if ($(this).attr("file_name") === "..") { 67 | path.pop(); 68 | } else { 69 | path.push($(this).attr("file_name")); 70 | } 71 | 72 | get_file_list(array2str(path)); 73 | 74 | let html = "
  • " 75 | path.forEach(function (item) { 76 | html = html + "
  • " + item + "
  • " 77 | }) 78 | $("#path").empty().append(html); 79 | } 80 | }) 81 | 82 | // 删除文件 83 | $("button.file-delete").click(function () { 84 | delete_file(array2str(path) + $(this).attr("file_name")); 85 | }) 86 | } 87 | 88 | let delete_file = function (_path) { 89 | let settings = { 90 | "async": true, 91 | "crossDomain": true, 92 | "url": "/api/file", 93 | "method": "DELETE", 94 | "headers": {"content-type": "application/json"}, 95 | "processData": false, 96 | "data": JSON.stringify({ 97 | "path": _path 98 | }) 99 | }; 100 | 101 | $.ajax(settings).done(function (response) { 102 | get_file_list(array2str(path)); 103 | 104 | }); 105 | } 106 | 107 | let upload_file = function (_path) { 108 | let form = new FormData(); 109 | form.append("file", $('#upfile').prop("files")[0]); 110 | // form.append("file", $('#upfile').prop('files').files[0]); 111 | form.append("path", _path); 112 | 113 | console.log(form) 114 | 115 | let settings = { 116 | "url": "/api/file", 117 | "method": "PATCH", 118 | "timeout": 0, 119 | "processData": false, 120 | "mimeType": "multipart/form-data", 121 | "contentType": false, 122 | "data": form 123 | }; 124 | 125 | $.ajax(settings).done(function (response) { 126 | get_file_list(array2str(path)); 127 | }); 128 | } 129 | 130 | $(".pagination li:first").click(function () { 131 | page_index -= 1; 132 | $("#page-index").html('' + page_index + ''); 133 | get_file_list_by_index(page_index - 1); 134 | }) 135 | 136 | $(".pagination li:last").click(function () { 137 | page_index += 1; 138 | $("#page-index").html('' + page_index + ''); 139 | get_file_list_by_index(page_index - 1); 140 | }) 141 | 142 | 143 | get_file_list(array2str(path)); 144 | 145 | $("#upfile").change(function () { 146 | upload_file(array2str(path)) 147 | }) 148 | }) -------------------------------------------------------------------------------- /static/js/ftp.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | let path = []; 4 | let page_index = 1; 5 | let list_num = 15; 6 | let file_list; 7 | 8 | let array2str = function (path_array) { 9 | let path_str = "/" 10 | path_array.forEach(function (item) { 11 | path_str = path_str + item + "/" 12 | }) 13 | return path_str; 14 | } 15 | 16 | let get_file_list = function (_path, change = true) { 17 | let settings = { 18 | "async": true, 19 | "crossDomain": true, 20 | "url": "/api/ftp_list?path=" + _path, 21 | "method": "GET" 22 | }; 23 | 24 | $.ajax(settings).done(function (response) { 25 | if (typeof (response) === "string") { 26 | response = JSON.parse(response) 27 | } 28 | 29 | if (change) { 30 | page_index = 1; 31 | $("#page-index").html('' + page_index + ''); 32 | } 33 | file_list = response["file"]; 34 | get_file_list_by_index(page_index - 1); 35 | }); 36 | } 37 | 38 | let get_file_list_by_index = function (index) { 39 | let html = ""; 40 | for (let i = index * list_num; i < Math.min((index + 1) * list_num, file_list.length); ++i) { 41 | html = html + '' + 42 | '' + 43 | ' ' + 44 | '       ' + 45 | ' ' + 46 | '    ' + file_list[i]["name"] + 47 | ' ' + 48 | ' ' + file_list[i]["mode"] + '' + 49 | ' ' + file_list[i]["owner"] + '' + 50 | ' ' + 51 | '
    ' + 52 | ' 下载' + 53 | ' ' + 54 | // ' ' + 55 | '
    ' + 56 | ' ' + 57 | '' 58 | } 59 | $("#file-list").empty().append(html); 60 | 61 | // 文件夹点击事件 62 | $("td.name").click(function () { 63 | if ($(this).attr("file_type") === "dir") { 64 | if ($(this).attr("file_name") === ".") { 65 | 66 | } else if ($(this).attr("file_name") === "..") { 67 | path.pop(); 68 | } else { 69 | path.push($(this).attr("file_name")); 70 | } 71 | 72 | get_file_list(array2str(path)); 73 | 74 | let html = "
  • " 75 | path.forEach(function (item) { 76 | html = html + "
  • " + item + "
  • " 77 | }) 78 | $("#path").empty().append(html); 79 | } 80 | }) 81 | 82 | // 删除文件 83 | $("button.file-delete").click(function () { 84 | delete_file(array2str(path) + $(this).attr("file_name")); 85 | }) 86 | } 87 | 88 | let delete_file = function (_path) { 89 | let settings = { 90 | "async": true, 91 | "crossDomain": true, 92 | "url": "/api/ftp", 93 | "method": "DELETE", 94 | "headers": {"content-type": "application/json"}, 95 | "processData": false, 96 | "data": JSON.stringify({ 97 | "path": _path 98 | }) 99 | }; 100 | 101 | $.ajax(settings).done(function (response) { 102 | get_file_list(array2str(path)); 103 | 104 | }); 105 | } 106 | 107 | let upload_file = function (_path) { 108 | let form = new FormData(); 109 | form.append("file", $('#upfile').prop("files")[0]); 110 | // form.append("file", $('#upfile').prop('files').files[0]); 111 | form.append("path", _path); 112 | 113 | console.log(form) 114 | 115 | let settings = { 116 | "url": "/api/ftp", 117 | "method": "PATCH", 118 | "timeout": 0, 119 | "processData": false, 120 | "mimeType": "multipart/form-data", 121 | "contentType": false, 122 | "data": form 123 | }; 124 | 125 | $.ajax(settings).done(function (response) { 126 | get_file_list(array2str(path)); 127 | }); 128 | } 129 | 130 | $(".pagination li:first").click(function () { 131 | page_index -= 1; 132 | $("#page-index").html('' + page_index + ''); 133 | get_file_list_by_index(page_index - 1); 134 | }) 135 | 136 | $(".pagination li:last").click(function () { 137 | page_index += 1; 138 | $("#page-index").html('' + page_index + ''); 139 | get_file_list_by_index(page_index - 1); 140 | }) 141 | 142 | 143 | get_file_list(array2str(path)); 144 | 145 | $("#upfile").change(function () { 146 | upload_file(array2str(path)) 147 | }) 148 | }) -------------------------------------------------------------------------------- /static/js/index.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | let settings = { 4 | "async": true, 5 | "crossDomain": true, 6 | "url": "/api/sysinfo", 7 | "method": "GET", 8 | "headers": { 9 | "content-type": "application/xml", 10 | } 11 | }; 12 | 13 | let init_axis_data = function (len) { 14 | let now = new Date(); 15 | let res = []; 16 | while (len--) { 17 | res.unshift(now.toLocaleTimeString().replace(/^\D*/, '')); 18 | now = new Date(now - 2000); 19 | } 20 | return res; 21 | } 22 | 23 | let init_charts_data = function (len) { 24 | let res = [] 25 | while (len--) { 26 | res.push(0); 27 | } 28 | return res; 29 | } 30 | 31 | let shift_data = function (myChart, option, a, b) { 32 | let axisData = (new Date()).toLocaleTimeString().replace(/^\D*/, ''); 33 | 34 | let data0 = option.series[0].data; 35 | let data1 = option.series[1].data; 36 | data0.shift(); 37 | data0.push(a); 38 | data1.shift(); 39 | data1.push(b); 40 | 41 | option.xAxis[0].data.shift(); 42 | option.xAxis[0].data.push(axisData); 43 | myChart.setOption(option); 44 | } 45 | 46 | let net_io_charts = echarts.init(document.getElementById("net-io")); 47 | let net_io_colors = ['#5793f3', '#d14a61', '#675bba']; 48 | let net_io_option = { 49 | color: net_io_colors, 50 | tooltip: { 51 | trigger: 'none', 52 | axisPointer: { 53 | type: 'cross' 54 | } 55 | }, 56 | legend: { 57 | data: ['网络发送', '网络接收'] 58 | }, 59 | grid: { 60 | top: 70, 61 | bottom: 50 62 | }, 63 | xAxis: [ 64 | { 65 | type: 'category', 66 | axisTick: { 67 | alignWithLabel: true 68 | }, 69 | axisLine: { 70 | onZero: false, 71 | lineStyle: { 72 | color: net_io_colors[1] 73 | } 74 | }, 75 | axisPointer: { 76 | label: { 77 | formatter: function (params) { 78 | return '网络接收 ' + params.value 79 | + (params.seriesData.length ? ':' + params.seriesData[0].data : ''); 80 | } 81 | } 82 | }, 83 | data: init_axis_data(12) 84 | }, 85 | { 86 | type: 'category', 87 | axisTick: { 88 | alignWithLabel: true 89 | }, 90 | axisLine: { 91 | onZero: false, 92 | lineStyle: { 93 | color: net_io_colors[0] 94 | } 95 | }, 96 | axisPointer: { 97 | label: { 98 | formatter: function (params) { 99 | return '网络发送 ' + params.value 100 | + (params.seriesData.length ? ':' + params.seriesData[0].data : ''); 101 | } 102 | } 103 | }, 104 | data: init_axis_data(12) 105 | } 106 | ], 107 | yAxis: [ 108 | { 109 | type: 'value', 110 | max: 2000, 111 | min: 0 112 | } 113 | ], 114 | series: [ 115 | { 116 | name: '网络发送', 117 | type: 'line', 118 | xAxisIndex: 1, 119 | smooth: true, 120 | data: init_charts_data(12), 121 | }, 122 | { 123 | name: '网络接收', 124 | type: 'line', 125 | smooth: true, 126 | data: init_charts_data(12), 127 | } 128 | ] 129 | }; 130 | net_io_charts.setOption(net_io_option); 131 | 132 | 133 | let io_charts = echarts.init(document.getElementById("io")); 134 | let io_option = { 135 | color: net_io_colors, 136 | 137 | tooltip: { 138 | trigger: 'none', 139 | axisPointer: { 140 | type: 'cross' 141 | } 142 | }, 143 | legend: { 144 | data: ['发送', '接收'] 145 | }, 146 | grid: { 147 | top: 70, 148 | bottom: 50 149 | }, 150 | xAxis: [ 151 | { 152 | type: 'category', 153 | axisTick: { 154 | alignWithLabel: true 155 | }, 156 | axisLine: { 157 | onZero: false, 158 | lineStyle: { 159 | color: net_io_colors[1] 160 | } 161 | }, 162 | axisPointer: { 163 | label: { 164 | formatter: function (params) { 165 | return '接受 ' + params.value 166 | + (params.seriesData.length ? ':' + params.seriesData[0].data : ''); 167 | } 168 | } 169 | }, 170 | data: init_axis_data(12) 171 | }, 172 | { 173 | type: 'category', 174 | axisTick: { 175 | alignWithLabel: true 176 | }, 177 | axisLine: { 178 | onZero: false, 179 | lineStyle: { 180 | color: net_io_colors[0] 181 | } 182 | }, 183 | axisPointer: { 184 | label: { 185 | formatter: function (params) { 186 | return '发送 ' + params.value 187 | + (params.seriesData.length ? ':' + params.seriesData[0].data : ''); 188 | } 189 | } 190 | }, 191 | data: init_axis_data(12) 192 | } 193 | ], 194 | yAxis: [ 195 | { 196 | type: 'value' 197 | } 198 | ], 199 | series: [ 200 | { 201 | name: '发送', 202 | type: 'line', 203 | xAxisIndex: 1, 204 | smooth: true, 205 | data: init_charts_data(12) 206 | }, 207 | { 208 | name: '接收', 209 | type: 'line', 210 | smooth: true, 211 | data: init_charts_data(12) 212 | } 213 | ] 214 | }; 215 | io_charts.setOption(io_option); 216 | 217 | let vm = new Vue({ 218 | el: '#cpu_info', 219 | data: { 220 | // core_num: response, 221 | message: { 222 | "cpu_info": { 223 | "core_num": 2, 224 | "cpu_num": 1, 225 | "model_name": "Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz", 226 | "processor_num": 2 227 | }, 228 | "disk_info": { 229 | "Total": 39, 230 | "Used": 29 231 | }, 232 | "io_info": { 233 | "io_recv": 0, 234 | "io_sent": 0, 235 | "net_io_recv": 0, 236 | "net_io_sent": 0 237 | }, 238 | "mem_info": { 239 | "Active": 2.71, 240 | "Buffers": 0.3, 241 | "Cached": 0.75, 242 | "MemAvailable": 1.4, 243 | "MemFree": 0.51, 244 | "MemTotal": 3.85, 245 | "SwapCached": 0, 246 | "Used": 2.29 247 | } 248 | } 249 | } 250 | }); 251 | 252 | setInterval(function () { 253 | 254 | $.ajax(settings).done(function (response) { 255 | if (typeof (response) === "string") { 256 | response = JSON.parse(response) 257 | } 258 | 259 | shift_data(net_io_charts, net_io_option, response['io_info']['net_io_sent'], response['io_info']['net_io_recv']); 260 | shift_data(io_charts, io_option, response['io_info']['io_sent'], response['io_info']['io_recv']); 261 | 262 | let disk_charts = echarts.init(document.getElementById("disk")); 263 | let disk_option = { 264 | tooltip: { 265 | trigger: 'item', 266 | formatter: 'Used: {c}G' 267 | }, 268 | angleAxis: { 269 | max: response['disk_info']['Total'], 270 | startAngle: 30, 271 | splitLine: { 272 | show: false 273 | } 274 | }, 275 | radiusAxis: { 276 | type: 'category', 277 | data: ['', '/'], 278 | z: 10 279 | }, 280 | polar: {}, 281 | series: [{ 282 | type: 'bar', 283 | data: [0, response['disk_info']['Used']], 284 | coordinateSystem: 'polar', 285 | name: '/', 286 | color: 'rgba(200, 0, 0, 0.5)', 287 | itemStyle: { 288 | borderColor: 'red', 289 | borderWidth: 1 290 | } 291 | }], 292 | legend: { 293 | show: true, 294 | data: ['/'] 295 | } 296 | }; 297 | disk_charts.setOption(disk_option) 298 | 299 | let mem_charts = echarts.init(document.getElementById("mem")); 300 | let mem_option = { 301 | tooltip: { 302 | trigger: 'item', 303 | formatter: '{a}
    {b}: {c} ({d}%)' 304 | }, 305 | legend: { 306 | orient: 'horizontal', 307 | // left: 10, 308 | data: ['Used', 'Free', 'Buffers', 'Cached'] 309 | }, 310 | series: [ 311 | { 312 | name: '内存占用', 313 | type: 'pie', 314 | radius: ['50%', '70%'], 315 | avoidLabelOverlap: false, 316 | label: { 317 | show: false, 318 | position: 'center' 319 | }, 320 | emphasis: { 321 | label: { 322 | show: true, 323 | fontSize: '30', 324 | fontWeight: 'bold' 325 | } 326 | }, 327 | labelLine: { 328 | show: false 329 | }, 330 | data: [ 331 | {value: response['mem_info']['Used'], name: 'Used'}, 332 | {value: response['mem_info']['MemFree'], name: 'Free'}, 333 | {value: response['mem_info']['Buffers'], name: 'Buffers'}, 334 | {value: response['mem_info']['Cached'], name: 'Cached'} 335 | ] 336 | } 337 | ] 338 | }; 339 | mem_charts.setOption(mem_option) 340 | 341 | vm.message = response 342 | }); 343 | }, 2000); 344 | 345 | $("#logout").click(function () { 346 | let settings = { 347 | "url": "/api/logout", 348 | "method": "POST", 349 | "timeout": 0, 350 | }; 351 | 352 | $.ajax(settings).done(function (response) { 353 | window.location.href = "http://" + window.location.host; 354 | }); 355 | }) 356 | }) -------------------------------------------------------------------------------- /static/js/login.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | $("#login-btn").click(function () { 3 | let settings = { 4 | "async": true, 5 | "crossDomain": true, 6 | "url": "/api/login", 7 | "method": "POST", 8 | "headers": {"content-type": "application/json"}, 9 | "processData": false, 10 | "data": JSON.stringify({ 11 | "name": $("#inputEmailAddress").val(), 12 | "passwd": $("#inputPassword").val() 13 | }) 14 | }; 15 | 16 | $.ajax(settings).done(function (response) { 17 | if (response["status"] === "OK") { 18 | window.location.href = "http://" + window.location.host; 19 | } 20 | }); 21 | }) 22 | 23 | 24 | }) 25 | -------------------------------------------------------------------------------- /static/js/nginx.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | let delete_web = function (name) { 4 | let settings = { 5 | "url": "/api/nginx", 6 | "method": "DELETE", 7 | "timeout": 0, 8 | "headers": { 9 | "Content-Type": "application/x-www-form-urlencoded" 10 | }, 11 | "data": {"name": name} 12 | }; 13 | 14 | $.ajax(settings).done(function (response) { 15 | get_web_list() 16 | }); 17 | } 18 | 19 | let show_web_list = function (web_array) { 20 | console.log(web_array) 21 | let $dummy = $("#web-list") 22 | $dummy.empty() 23 | for (let i = 0; i < web_array.length; ++i) { 24 | if (i % 2 === 0) { 25 | $dummy.append('
    ') 26 | } 27 | let html = '' + 28 | '
    ' + 29 | '
    ' + 30 | '
    ' + 31 | '
    ' + 32 | '
    ' + 33 | ' ' + 34 | '
    ' + 35 | '
    ' + 36 | '
    ' + web_array[i]['port'] + '
    ' + 37 | '
    ' + web_array[i]['name'] + '
    ' + 38 | '
    ' + 39 | '
    ' + 40 | '
    ' + 41 | ' ' + 51 | '
    ' + 52 | '
    ' 53 | $dummy.children("div.row").last().append(html) 54 | } 55 | 56 | $("a.pull-left").click(function () { 57 | delete_web($(this).attr("web_name")) 58 | }) 59 | } 60 | 61 | let get_web_list = function () { 62 | let settings = { 63 | "url": "/api/nginx", 64 | "method": "GET", 65 | "timeout": 0, 66 | }; 67 | 68 | $.ajax(settings).done(function (response) { 69 | if (typeof (response) === "string") { 70 | response = JSON.parse(response) 71 | } 72 | show_web_list(response["result"]) 73 | }); 74 | } 75 | 76 | get_web_list() 77 | 78 | let upload_file = function () { 79 | let form = new FormData(); 80 | form.append("file", $('#file').prop("files")[0]); 81 | form.append("port", $("#port").val()); 82 | 83 | console.log(form) 84 | 85 | let settings = { 86 | "url": "/api/nginx", 87 | "method": "PATCH", 88 | "timeout": 0, 89 | "processData": false, 90 | "mimeType": "multipart/form-data", 91 | "contentType": false, 92 | "data": form 93 | }; 94 | 95 | $.ajax(settings).done(function (response) { 96 | get_web_list() 97 | }); 98 | } 99 | 100 | $("#upfile").click(function () { 101 | upload_file() 102 | }) 103 | }) -------------------------------------------------------------------------------- /static/js/process.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | let stop_process = function (_pid) { 4 | let settings = { 5 | "url": "/api/process", 6 | "method": "DELETE", 7 | "timeout": 0, 8 | "headers": {"Content-Type": "application/x-www-form-urlencoded"}, 9 | "data": {"pid": _pid} 10 | }; 11 | 12 | $.ajax(settings).done(function (response) { 13 | // get_process() 14 | }); 15 | } 16 | 17 | let show_process = function (proc_array) { 18 | let html = ""; 19 | proc_array.forEach(function (item) { 20 | html = html + '' + 21 | '' + 22 | ' ' + item["proc_name"] + '' + 23 | ' ' + item["cpu"] + '' + 24 | ' ' + item["mem"] + '' + 25 | ' ' + item["status"] + '' + 26 | ' ' + 27 | ' ' + 28 | ' ' + 29 | '' 30 | }) 31 | $("#proc-list").empty().append(html); 32 | 33 | $("td > .btn").click(function () { 34 | stop_process($(this).parent().parent().attr("pid")) 35 | }) 36 | } 37 | 38 | let get_process = function () { 39 | let settings = { 40 | "url": "/api/process", 41 | "method": "GET", 42 | "timeout": 0, 43 | }; 44 | $.ajax(settings).done(function (response) { 45 | show_process(response); 46 | }); 47 | } 48 | 49 | // get_process(); 50 | setInterval(get_process, 1000); 51 | 52 | let show_netstat = function (netstat_array) { 53 | let $dummy = $("#netstat-list") 54 | $dummy.empty() 55 | let html = "" 56 | netstat_array.forEach(function (item) { 57 | html += '' + 58 | '' + 59 | ' ' + item['proc_name'] + '' + 60 | ' ' + item['cpu'] + '' + 61 | ' ' + item['mem'] + '' + 62 | ' ' + item['laddr_ip'] + '' + 63 | ' ' + item['laddr_port'] + '' + 64 | ' ' + item['raddr_ip'] + '' + 65 | ' ' + item['raddr_port'] + '' + 66 | '' 67 | }) 68 | $dummy.append(html) 69 | } 70 | 71 | let get_netstat = function () { 72 | let settings = { 73 | "url": "/api/netstat", 74 | "method": "GET", 75 | "timeout": 0, 76 | }; 77 | 78 | $.ajax(settings).done(function (response) { 79 | show_netstat(response) 80 | }); 81 | } 82 | 83 | setInterval(get_netstat, 1000); 84 | }) -------------------------------------------------------------------------------- /static/js/ufw.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | //////////////////////////////////////////////////////////// 4 | /// /// 5 | //////////////////////////////////////////////////////////// 6 | 7 | let show_port_list = function (port_array) { 8 | let $dummy = $("#port-list") 9 | $dummy.empty() 10 | let html = '' 11 | port_array.forEach(function (item) { 12 | html += '' + 13 | '' + 14 | ' ' + item['rule_id'] + '' + 15 | ' ' + item['port'] + '' + 16 | ' ' + item['protocol'] + '' + 17 | ' ' + item['description'] + '' + 18 | ' ' + 19 | ' 删除' + 22 | ' ' + 23 | '' 24 | }) 25 | $dummy.append(html) 26 | 27 | $("#port-list a").on("click", function () { 28 | delete_port( 29 | $(this).attr('port'), 30 | $(this).attr('protocol') 31 | ) 32 | }) 33 | } 34 | 35 | let get_port_list = function () { 36 | let settings = { 37 | "url": "/api/ufw/port", 38 | "method": "GET", 39 | "timeout": 0, 40 | }; 41 | 42 | $.ajax(settings).done(function (response) { 43 | show_port_list(response) 44 | }); 45 | } 46 | 47 | get_port_list() 48 | 49 | //////////////////////////////////////////////////////////// 50 | /// /// 51 | //////////////////////////////////////////////////////////// 52 | 53 | let delete_port = function (port, protocol) { 54 | let settings = { 55 | "url": "/api/ufw/port", 56 | "method": "DELETE", 57 | "timeout": 0, 58 | "headers": {"Content-Type": "application/x-www-form-urlencoded"}, 59 | "data": { 60 | "port": port, 61 | "protocol": protocol 62 | } 63 | }; 64 | 65 | $.ajax(settings).done(function (response) { 66 | get_port_list() 67 | }); 68 | } 69 | 70 | //////////////////////////////////////////////////////////// 71 | /// /// 72 | //////////////////////////////////////////////////////////// 73 | 74 | let post_port = function (port, protocol, description) { 75 | let settings = { 76 | "url": "/api/ufw/port", 77 | "method": "POST", 78 | "timeout": 0, 79 | "headers": {"Content-Type": "application/x-www-form-urlencoded"}, 80 | "data": { 81 | "port": port, 82 | "protocol": protocol, 83 | "description": description 84 | } 85 | }; 86 | 87 | $.ajax(settings).done(function (response) { 88 | get_port_list() 89 | }); 90 | } 91 | 92 | $("#port-submit").on("click", function () { 93 | post_port( 94 | $("#port").val(), 95 | $("#protocol1").val(), 96 | $("#description1").val() 97 | ) 98 | }) 99 | 100 | //////////////////////////////////////////////////////////// 101 | /// /// 102 | //////////////////////////////////////////////////////////// 103 | 104 | let show_ip_list = function (port_array) { 105 | let $dummy = $("#ip-list") 106 | $dummy.empty() 107 | let html = '' 108 | port_array.forEach(function (item) { 109 | html += '' + 110 | '' + 111 | ' ' + item['rule_id'] + '' + 112 | ' ' + item['ip'] + '' + 113 | ' ' + item['protocol'] + '' + 114 | ' ' + item['description'] + '' + 115 | ' ' + 116 | ' 删除' + 119 | ' ' + 120 | '' 121 | }) 122 | $dummy.append(html) 123 | 124 | $("#ip-list a").on("click", function () { 125 | delete_ip( 126 | $(this).attr('ip'), 127 | $(this).attr('protocol') 128 | ) 129 | }) 130 | } 131 | 132 | let get_ip_list = function () { 133 | let settings = { 134 | "url": "/api/ufw/ip", 135 | "method": "GET", 136 | "timeout": 0, 137 | }; 138 | 139 | $.ajax(settings).done(function (response) { 140 | show_ip_list(response) 141 | }); 142 | } 143 | 144 | get_ip_list() 145 | 146 | //////////////////////////////////////////////////////////// 147 | /// /// 148 | //////////////////////////////////////////////////////////// 149 | 150 | let post_ip = function (ip, protocol, description) { 151 | let settings = { 152 | "url": "/api/ufw/ip", 153 | "method": "POST", 154 | "timeout": 0, 155 | "headers": {"Content-Type": "application/x-www-form-urlencoded"}, 156 | "data": { 157 | "ip": ip, 158 | "protocol": protocol, 159 | "description": description 160 | } 161 | }; 162 | 163 | $.ajax(settings).done(function (response) { 164 | get_ip_list() 165 | }); 166 | } 167 | 168 | $("#ip-submit").on("click", function () { 169 | post_ip( 170 | $("#ip").val(), 171 | $("#protocol2").val(), 172 | $("#description2").val() 173 | ) 174 | }) 175 | 176 | //////////////////////////////////////////////////////////// 177 | /// /// 178 | //////////////////////////////////////////////////////////// 179 | 180 | let delete_ip = function (ip, protocol) { 181 | let settings = { 182 | "url": "/api/ufw/ip", 183 | "method": "DELETE", 184 | "timeout": 0, 185 | "headers": {"Content-Type": "application/x-www-form-urlencoded"}, 186 | "data": { 187 | "ip": ip, 188 | "protocol": protocol 189 | } 190 | }; 191 | 192 | $.ajax(settings).done(function (response) { 193 | get_ip_list() 194 | }); 195 | } 196 | }) -------------------------------------------------------------------------------- /static/lib/css/plugins/morris.css: -------------------------------------------------------------------------------- 1 | .morris-hover{position:absolute;z-index:1000}.morris-hover.morris-default-style{border-radius:10px;padding:6px;color:#666;background:rgba(255,255,255,0.8);border:solid 2px rgba(230,230,230,0.8);font-family:sans-serif;font-size:12px;text-align:center}.morris-hover.morris-default-style .morris-hover-row-label{font-weight:bold;margin:0.25em 0} 2 | .morris-hover.morris-default-style .morris-hover-point{white-space:nowrap;margin:0.1em 0} 3 | -------------------------------------------------------------------------------- /static/lib/css/sb-admin.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Start Bootstrap - SB Admin Bootstrap Admin Template (http://startbootstrap.com) 3 | * Code licensed under the Apache License v2.0. 4 | * For details, see http://www.apache.org/licenses/LICENSE-2.0. 5 | */ 6 | 7 | /* Global Styles */ 8 | 9 | body { 10 | margin-top: 100px; 11 | background-color: #222; 12 | } 13 | 14 | @media(min-width:768px) { 15 | body { 16 | margin-top: 50px; 17 | } 18 | } 19 | 20 | #wrapper { 21 | padding-left: 0; 22 | } 23 | 24 | #page-wrapper { 25 | width: 100%; 26 | padding: 0; 27 | background-color: #fff; 28 | } 29 | 30 | .huge { 31 | font-size: 50px; 32 | line-height: normal; 33 | } 34 | 35 | @media(min-width:768px) { 36 | #wrapper { 37 | padding-left: 225px; 38 | } 39 | 40 | #page-wrapper { 41 | padding: 10px; 42 | } 43 | } 44 | 45 | /* Top Navigation */ 46 | 47 | .top-nav { 48 | padding: 0 15px; 49 | } 50 | 51 | .top-nav>li { 52 | display: inline-block; 53 | float: left; 54 | } 55 | 56 | .top-nav>li>a { 57 | padding-top: 15px; 58 | padding-bottom: 15px; 59 | line-height: 20px; 60 | color: #999; 61 | } 62 | 63 | .top-nav>li>a:hover, 64 | .top-nav>li>a:focus, 65 | .top-nav>.open>a, 66 | .top-nav>.open>a:hover, 67 | .top-nav>.open>a:focus { 68 | color: #fff; 69 | background-color: #000; 70 | } 71 | 72 | .top-nav>.open>.dropdown-menu { 73 | float: left; 74 | position: absolute; 75 | margin-top: 0; 76 | border: 1px solid rgba(0,0,0,.15); 77 | border-top-left-radius: 0; 78 | border-top-right-radius: 0; 79 | background-color: #fff; 80 | -webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175); 81 | box-shadow: 0 6px 12px rgba(0,0,0,.175); 82 | } 83 | 84 | .top-nav>.open>.dropdown-menu>li>a { 85 | white-space: normal; 86 | } 87 | 88 | ul.message-dropdown { 89 | padding: 0; 90 | max-height: 250px; 91 | overflow-x: hidden; 92 | overflow-y: auto; 93 | } 94 | 95 | li.message-preview { 96 | width: 275px; 97 | border-bottom: 1px solid rgba(0,0,0,.15); 98 | } 99 | 100 | li.message-preview>a { 101 | padding-top: 15px; 102 | padding-bottom: 15px; 103 | } 104 | 105 | li.message-footer { 106 | margin: 5px 0; 107 | } 108 | 109 | ul.alert-dropdown { 110 | width: 200px; 111 | } 112 | 113 | /* Side Navigation */ 114 | 115 | @media(min-width:768px) { 116 | .side-nav { 117 | position: fixed; 118 | top: 51px; 119 | left: 225px; 120 | width: 225px; 121 | margin-left: -225px; 122 | border: none; 123 | border-radius: 0; 124 | overflow-y: auto; 125 | background-color: #222; 126 | bottom: 0; 127 | overflow-x: hidden; 128 | padding-bottom: 40px; 129 | } 130 | 131 | .side-nav>li>a { 132 | width: 225px; 133 | } 134 | 135 | .side-nav li a:hover, 136 | .side-nav li a:focus { 137 | outline: none; 138 | background-color: #000 !important; 139 | } 140 | } 141 | 142 | .side-nav>li>ul { 143 | padding: 0; 144 | } 145 | 146 | .side-nav>li>ul>li>a { 147 | display: block; 148 | padding: 10px 15px 10px 38px; 149 | text-decoration: none; 150 | color: #999; 151 | } 152 | 153 | .side-nav>li>ul>li>a:hover { 154 | color: #fff; 155 | } 156 | 157 | /* Flot Chart Containers */ 158 | 159 | .flot-chart { 160 | display: block; 161 | height: 400px; 162 | } 163 | 164 | .flot-chart-content { 165 | width: 100%; 166 | height: 100%; 167 | } 168 | 169 | /* Custom Colored Panels */ 170 | 171 | .huge { 172 | font-size: 40px; 173 | } 174 | 175 | .panel-green { 176 | border-color: #5cb85c; 177 | } 178 | 179 | .panel-green > .panel-heading { 180 | border-color: #5cb85c; 181 | color: #fff; 182 | background-color: #5cb85c; 183 | } 184 | 185 | .panel-green > a { 186 | color: #5cb85c; 187 | } 188 | 189 | .panel-green > a:hover { 190 | color: #3d8b3d; 191 | } 192 | 193 | .panel-red { 194 | border-color: #d9534f; 195 | } 196 | 197 | .panel-red > .panel-heading { 198 | border-color: #d9534f; 199 | color: #fff; 200 | background-color: #d9534f; 201 | } 202 | 203 | .panel-red > a { 204 | color: #d9534f; 205 | } 206 | 207 | .panel-red > a:hover { 208 | color: #b52b27; 209 | } 210 | 211 | .panel-yellow { 212 | border-color: #f0ad4e; 213 | } 214 | 215 | .panel-yellow > .panel-heading { 216 | border-color: #f0ad4e; 217 | color: #fff; 218 | background-color: #f0ad4e; 219 | } 220 | 221 | .panel-yellow > a { 222 | color: #f0ad4e; 223 | } 224 | 225 | .panel-yellow > a:hover { 226 | color: #df8a13; 227 | } -------------------------------------------------------------------------------- /static/lib/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/static/lib/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /static/lib/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/static/lib/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /static/lib/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/static/lib/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /static/lib/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/static/lib/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /static/lib/font-awesome/less/bordered-pulled.less: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em @fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .pull-right { float: right; } 11 | .pull-left { float: left; } 12 | 13 | .@{fa-css-prefix} { 14 | &.pull-left { margin-right: .3em; } 15 | &.pull-right { margin-left: .3em; } 16 | } 17 | -------------------------------------------------------------------------------- /static/lib/font-awesome/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal 14px/1 FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | -------------------------------------------------------------------------------- /static/lib/font-awesome/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /static/lib/font-awesome/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables.less"; 7 | @import "mixins.less"; 8 | @import "path.less"; 9 | @import "core.less"; 10 | @import "larger.less"; 11 | @import "fixed-width.less"; 12 | @import "list.less"; 13 | @import "bordered-pulled.less"; 14 | @import "spinning.less"; 15 | @import "rotated-flipped.less"; 16 | @import "stacked.less"; 17 | @import "icons.less"; 18 | -------------------------------------------------------------------------------- /static/lib/font-awesome/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .@{fa-css-prefix}-2x { font-size: 2em; } 11 | .@{fa-css-prefix}-3x { font-size: 3em; } 12 | .@{fa-css-prefix}-4x { font-size: 4em; } 13 | .@{fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /static/lib/font-awesome/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: (-@fa-li-width + (4em / 14)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /static/lib/font-awesome/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal 14px/1 FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | .fa-icon-rotate(@degrees, @rotation) { 14 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation); 15 | -webkit-transform: rotate(@degrees); 16 | -ms-transform: rotate(@degrees); 17 | transform: rotate(@degrees); 18 | } 19 | 20 | .fa-icon-flip(@horiz, @vert, @rotation) { 21 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1); 22 | -webkit-transform: scale(@horiz, @vert); 23 | -ms-transform: scale(@horiz, @vert); 24 | transform: scale(@horiz, @vert); 25 | } 26 | -------------------------------------------------------------------------------- /static/lib/font-awesome/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 9 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 10 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 11 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | -------------------------------------------------------------------------------- /static/lib/font-awesome/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } 5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } 6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } 7 | 8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } 9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .@{fa-css-prefix}-rotate-90, 15 | :root .@{fa-css-prefix}-rotate-180, 16 | :root .@{fa-css-prefix}-rotate-270, 17 | :root .@{fa-css-prefix}-flip-horizontal, 18 | :root .@{fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /static/lib/font-awesome/less/spinning.less: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | @-webkit-keyframes fa-spin { 10 | 0% { 11 | -webkit-transform: rotate(0deg); 12 | transform: rotate(0deg); 13 | } 14 | 100% { 15 | -webkit-transform: rotate(359deg); 16 | transform: rotate(359deg); 17 | } 18 | } 19 | 20 | @keyframes fa-spin { 21 | 0% { 22 | -webkit-transform: rotate(0deg); 23 | transform: rotate(0deg); 24 | } 25 | 100% { 26 | -webkit-transform: rotate(359deg); 27 | transform: rotate(359deg); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /static/lib/font-awesome/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; } 21 | -------------------------------------------------------------------------------- /static/lib/font-awesome/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em $fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .pull-right { float: right; } 11 | .pull-left { float: left; } 12 | 13 | .#{$fa-css-prefix} { 14 | &.pull-left { margin-right: .3em; } 15 | &.pull-right { margin-left: .3em; } 16 | } 17 | -------------------------------------------------------------------------------- /static/lib/font-awesome/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal 14px/1 FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | -------------------------------------------------------------------------------- /static/lib/font-awesome/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /static/lib/font-awesome/scss/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { font-size: 2em; } 11 | .#{$fa-css-prefix}-3x { font-size: 3em; } 12 | .#{$fa-css-prefix}-4x { font-size: 4em; } 13 | .#{$fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /static/lib/font-awesome/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .#{$fa-css-prefix}-li { 11 | position: absolute; 12 | left: -$fa-li-width; 13 | width: $fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.#{$fa-css-prefix}-lg { 17 | left: -$fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /static/lib/font-awesome/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal 14px/1 FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | @mixin fa-icon-rotate($degrees, $rotation) { 14 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); 15 | -webkit-transform: rotate($degrees); 16 | -ms-transform: rotate($degrees); 17 | transform: rotate($degrees); 18 | } 19 | 20 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 21 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); 22 | -webkit-transform: scale($horiz, $vert); 23 | -ms-transform: scale($horiz, $vert); 24 | transform: scale($horiz, $vert); 25 | } 26 | -------------------------------------------------------------------------------- /static/lib/font-awesome/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 9 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 10 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 11 | //src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | -------------------------------------------------------------------------------- /static/lib/font-awesome/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .#{$fa-css-prefix}-rotate-90, 15 | :root .#{$fa-css-prefix}-rotate-180, 16 | :root .#{$fa-css-prefix}-rotate-270, 17 | :root .#{$fa-css-prefix}-flip-horizontal, 18 | :root .#{$fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /static/lib/font-awesome/scss/_spinning.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | @-webkit-keyframes fa-spin { 10 | 0% { 11 | -webkit-transform: rotate(0deg); 12 | transform: rotate(0deg); 13 | } 14 | 100% { 15 | -webkit-transform: rotate(359deg); 16 | transform: rotate(359deg); 17 | } 18 | } 19 | 20 | @keyframes fa-spin { 21 | 0% { 22 | -webkit-transform: rotate(0deg); 23 | transform: rotate(0deg); 24 | } 25 | 100% { 26 | -webkit-transform: rotate(359deg); 27 | transform: rotate(359deg); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /static/lib/font-awesome/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; } 21 | -------------------------------------------------------------------------------- /static/lib/font-awesome/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "spinning"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | -------------------------------------------------------------------------------- /static/lib/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/static/lib/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/lib/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/static/lib/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/lib/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/static/lib/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /static/lib/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/static/lib/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /static/lib/js/plugins/flot/jquery.flot.resize.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for automatically redrawing plots as the placeholder resizes. 2 | 3 | Copyright (c) 2007-2013 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | It works by listening for changes on the placeholder div (through the jQuery 7 | resize event plugin) - if the size changes, it will redraw the plot. 8 | 9 | There are no options. If you need to disable the plugin for some plots, you 10 | can just fix the size of their placeholders. 11 | 12 | */ 13 | 14 | /* Inline dependency: 15 | * jQuery resize event - v1.1 - 3/14/2010 16 | * http://benalman.com/projects/jquery-resize-plugin/ 17 | * 18 | * Copyright (c) 2010 "Cowboy" Ben Alman 19 | * Dual licensed under the MIT and GPL licenses. 20 | * http://benalman.com/about/license/ 21 | */ 22 | 23 | (function($,h,c){var a=$([]),e=$.resize=$.extend($.resize,{}),i,k="setTimeout",j="resize",d=j+"-special-event",b="delay",f="throttleWindow";e[b]=250;e[f]=true;$.event.special[j]={setup:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.add(l);$.data(this,d,{w:l.width(),h:l.height()});if(a.length===1){g()}},teardown:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.not(l);l.removeData(d);if(!a.length){clearTimeout(i)}},add:function(l){if(!e[f]&&this[k]){return false}var n;function m(s,o,p){var q=$(this),r=$.data(this,d);r.w=o!==c?o:q.width();r.h=p!==c?p:q.height();n.apply(this,arguments)}if($.isFunction(l)){n=l;return m}else{n=l.handler;l.handler=m}}};function g(){i=h[k](function(){a.each(function(){var n=$(this),m=n.width(),l=n.height(),o=$.data(this,d);if(m!==o.w||l!==o.h){n.trigger(j,[o.w=m,o.h=l])}});g()},e[b])}})(jQuery,this); 24 | 25 | (function ($) { 26 | var options = { }; // no options 27 | 28 | function init(plot) { 29 | function onResize() { 30 | var placeholder = plot.getPlaceholder(); 31 | 32 | // somebody might have hidden us and we can't plot 33 | // when we don't have the dimensions 34 | if (placeholder.width() == 0 || placeholder.height() == 0) 35 | return; 36 | 37 | plot.resize(); 38 | plot.setupGrid(); 39 | plot.draw(); 40 | } 41 | 42 | function bindEvents(plot, eventHolder) { 43 | plot.getPlaceholder().resize(onResize); 44 | } 45 | 46 | function shutdown(plot, eventHolder) { 47 | plot.getPlaceholder().unbind("resize", onResize); 48 | } 49 | 50 | plot.hooks.bindEvents.push(bindEvents); 51 | plot.hooks.shutdown.push(shutdown); 52 | } 53 | 54 | $.plot.plugins.push({ 55 | init: init, 56 | options: options, 57 | name: 'resize', 58 | version: '1.0' 59 | }); 60 | })(jQuery); -------------------------------------------------------------------------------- /static/lib/js/plugins/flot/jquery.flot.tooltip.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jquery.flot.tooltip 3 | * 4 | * description: easy-to-use tooltips for Flot charts 5 | * version: 0.6.2 6 | * author: Krzysztof Urbas @krzysu [myviews.pl] 7 | * website: https://github.com/krzysu/flot.tooltip 8 | * 9 | * build on 2013-09-30 10 | * released under MIT License, 2012 11 | */ 12 | (function(t){var o={tooltip:!1,tooltipOpts:{content:"%s | X: %x | Y: %y",xDateFormat:null,yDateFormat:null,shifts:{x:10,y:20},defaultTheme:!0,onHover:function(){}}},i=function(t){this.tipPosition={x:0,y:0},this.init(t)};i.prototype.init=function(o){function i(t){var o={};o.x=t.pageX,o.y=t.pageY,s.updateTooltipPosition(o)}function e(t,o,i){var e=s.getDomElement();if(i){var n;n=s.stringFormat(s.tooltipOptions.content,i),e.html(n),s.updateTooltipPosition({x:o.pageX,y:o.pageY}),e.css({left:s.tipPosition.x+s.tooltipOptions.shifts.x,top:s.tipPosition.y+s.tooltipOptions.shifts.y}).show(),"function"==typeof s.tooltipOptions.onHover&&s.tooltipOptions.onHover(i,e)}else e.hide().html("")}var s=this;o.hooks.bindEvents.push(function(o,n){s.plotOptions=o.getOptions(),s.plotOptions.tooltip!==!1&&void 0!==s.plotOptions.tooltip&&(s.tooltipOptions=s.plotOptions.tooltipOpts,s.getDomElement(),t(o.getPlaceholder()).bind("plothover",e),t(n).bind("mousemove",i))}),o.hooks.shutdown.push(function(o,s){t(o.getPlaceholder()).unbind("plothover",e),t(s).unbind("mousemove",i)})},i.prototype.getDomElement=function(){var o;return t("#flotTip").length>0?o=t("#flotTip"):(o=t("
    ").attr("id","flotTip"),o.appendTo("body").hide().css({position:"absolute"}),this.tooltipOptions.defaultTheme&&o.css({background:"#fff","z-index":"100",padding:"0.4em 0.6em","border-radius":"0.5em","font-size":"0.8em",border:"1px solid #111",display:"none","white-space":"nowrap"})),o},i.prototype.updateTooltipPosition=function(o){var i=t("#flotTip").outerWidth()+this.tooltipOptions.shifts.x,e=t("#flotTip").outerHeight()+this.tooltipOptions.shifts.y;o.x-t(window).scrollLeft()>t(window).innerWidth()-i&&(o.x-=i),o.y-t(window).scrollTop()>t(window).innerHeight()-e&&(o.y-=e),this.tipPosition.x=o.x,this.tipPosition.y=o.y},i.prototype.stringFormat=function(t,o){var i=/%p\.{0,1}(\d{0,})/,e=/%s/,s=/%x\.{0,1}(?:\d{0,})/,n=/%y\.{0,1}(?:\d{0,})/;return"function"==typeof t&&(t=t(o.series.label,o.series.data[o.dataIndex][0],o.series.data[o.dataIndex][1],o)),o.series.percent!==void 0&&(t=this.adjustValPrecision(i,t,o.series.percent)),o.series.label!==void 0&&(t=t.replace(e,o.series.label)),this.isTimeMode("xaxis",o)&&this.isXDateFormat(o)&&(t=t.replace(s,this.timestampToDate(o.series.data[o.dataIndex][0],this.tooltipOptions.xDateFormat))),this.isTimeMode("yaxis",o)&&this.isYDateFormat(o)&&(t=t.replace(n,this.timestampToDate(o.series.data[o.dataIndex][1],this.tooltipOptions.yDateFormat))),"number"==typeof o.series.data[o.dataIndex][0]&&(t=this.adjustValPrecision(s,t,o.series.data[o.dataIndex][0])),"number"==typeof o.series.data[o.dataIndex][1]&&(t=this.adjustValPrecision(n,t,o.series.data[o.dataIndex][1])),o.series.xaxis.tickFormatter!==void 0&&(t=t.replace(s,o.series.xaxis.tickFormatter(o.series.data[o.dataIndex][0],o.series.xaxis))),o.series.yaxis.tickFormatter!==void 0&&(t=t.replace(n,o.series.yaxis.tickFormatter(o.series.data[o.dataIndex][1],o.series.yaxis))),t},i.prototype.isTimeMode=function(t,o){return o.series[t].options.mode!==void 0&&"time"===o.series[t].options.mode},i.prototype.isXDateFormat=function(){return this.tooltipOptions.xDateFormat!==void 0&&null!==this.tooltipOptions.xDateFormat},i.prototype.isYDateFormat=function(){return this.tooltipOptions.yDateFormat!==void 0&&null!==this.tooltipOptions.yDateFormat},i.prototype.timestampToDate=function(o,i){var e=new Date(o);return t.plot.formatDate(e,i)},i.prototype.adjustValPrecision=function(t,o,i){var e,s=o.match(t);return null!==s&&""!==RegExp.$1&&(e=RegExp.$1,i=i.toFixed(e),o=o.replace(t,i)),o};var e=function(t){new i(t)};t.plot.plugins.push({init:e,options:o,name:"tooltip",version:"0.6.1"})})(jQuery); -------------------------------------------------------------------------------- /static/lib/js/plugins/morris/morris-data.js: -------------------------------------------------------------------------------- 1 | // Morris.js Charts sample data for SB Admin template 2 | 3 | $(function () { 4 | 5 | // Area Chart 6 | Morris.Area({ 7 | element: 'morris-area-net-io', 8 | data: [{ 9 | period: '2010 Q1', 10 | iphone: 2666, 11 | ipad: null, 12 | itouch: 2647 13 | }, { 14 | period: '2010 Q2', 15 | iphone: 2778, 16 | ipad: 2294, 17 | itouch: 2441 18 | }, { 19 | period: '2010 Q3', 20 | iphone: 4912, 21 | ipad: 1969, 22 | itouch: 2501 23 | }, { 24 | period: '2010 Q4', 25 | iphone: 3767, 26 | ipad: 3597, 27 | itouch: 5689 28 | }, { 29 | period: '2011 Q1', 30 | iphone: 6810, 31 | ipad: 1914, 32 | itouch: 2293 33 | }, { 34 | period: '2011 Q2', 35 | iphone: 5670, 36 | ipad: 4293, 37 | itouch: 1881 38 | }, { 39 | period: '2011 Q3', 40 | iphone: 4820, 41 | ipad: 3795, 42 | itouch: 1588 43 | }, { 44 | period: '2011 Q4', 45 | iphone: 15073, 46 | ipad: 5967, 47 | itouch: 5175 48 | }, { 49 | period: '2012 Q1', 50 | iphone: 10687, 51 | ipad: 4460, 52 | itouch: 2028 53 | }, { 54 | period: '2012 Q2', 55 | iphone: 8432, 56 | ipad: 5713, 57 | itouch: 1791 58 | }], 59 | xkey: 'period', 60 | ykeys: ['iphone', 'ipad', 'itouch'], 61 | labels: ['iPhone', 'iPad', 'iPod Touch'], 62 | pointSize: 2, 63 | hideHover: 'auto', 64 | resize: true 65 | }); 66 | 67 | 68 | Morris.Area({ 69 | element: 'morris-area-io', 70 | data: [{ 71 | period: '2010 Q1', 72 | iphone: 2666, 73 | ipad: null, 74 | itouch: 2647 75 | }, { 76 | period: '2010 Q2', 77 | iphone: 2778, 78 | ipad: 2294, 79 | itouch: 2441 80 | }, { 81 | period: '2010 Q3', 82 | iphone: 4912, 83 | ipad: 1969, 84 | itouch: 2501 85 | }, { 86 | period: '2010 Q4', 87 | iphone: 3767, 88 | ipad: 3597, 89 | itouch: 5689 90 | }, { 91 | period: '2011 Q1', 92 | iphone: 6810, 93 | ipad: 1914, 94 | itouch: 2293 95 | }, { 96 | period: '2011 Q2', 97 | iphone: 5670, 98 | ipad: 4293, 99 | itouch: 1881 100 | }, { 101 | period: '2011 Q3', 102 | iphone: 4820, 103 | ipad: 3795, 104 | itouch: 1588 105 | }, { 106 | period: '2011 Q4', 107 | iphone: 15073, 108 | ipad: 5967, 109 | itouch: 5175 110 | }, { 111 | period: '2012 Q1', 112 | iphone: 10687, 113 | ipad: 4460, 114 | itouch: 2028 115 | }, { 116 | period: '2012 Q2', 117 | iphone: 8432, 118 | ipad: 5713, 119 | itouch: 1791 120 | }], 121 | xkey: 'period', 122 | ykeys: ['iphone', 'ipad', 'itouch'], 123 | labels: ['iPhone', 'iPad', 'iPod Touch'], 124 | pointSize: 2, 125 | hideHover: 'auto', 126 | resize: true 127 | }); 128 | 129 | // Donut Chart 130 | Morris.Donut({ 131 | element: 'morris-donut-disk', 132 | data: [{ 133 | label: "Download Sales", 134 | value: 12 135 | }, { 136 | label: "In-Store Sales", 137 | value: 30 138 | }, { 139 | label: "Mail-Order Sales", 140 | value: 20 141 | }], 142 | resize: true 143 | }); 144 | 145 | Morris.Donut({ 146 | element: 'morris-donut-mem', 147 | data: [{ 148 | label: "Download Sales", 149 | value: 12 150 | }, { 151 | label: "In-Store Sales", 152 | value: 30 153 | }, { 154 | label: "Mail-Order Sales", 155 | value: 20 156 | }], 157 | resize: true 158 | }); 159 | 160 | // Line Chart 161 | Morris.Line({ 162 | // ID of the element in which to draw the chart. 163 | element: 'morris-line-chart', 164 | // Chart data records -- each entry in this array corresponds to a point on 165 | // the chart. 166 | data: [{ 167 | d: '2012-10-01', 168 | visits: 802 169 | }, { 170 | d: '2012-10-02', 171 | visits: 783 172 | }, { 173 | d: '2012-10-03', 174 | visits: 820 175 | }, { 176 | d: '2012-10-04', 177 | visits: 839 178 | }, { 179 | d: '2012-10-05', 180 | visits: 792 181 | }, { 182 | d: '2012-10-06', 183 | visits: 859 184 | }, { 185 | d: '2012-10-07', 186 | visits: 790 187 | }, { 188 | d: '2012-10-08', 189 | visits: 1680 190 | }, { 191 | d: '2012-10-09', 192 | visits: 1592 193 | }, { 194 | d: '2012-10-10', 195 | visits: 1420 196 | }, { 197 | d: '2012-10-11', 198 | visits: 882 199 | }, { 200 | d: '2012-10-12', 201 | visits: 889 202 | }, { 203 | d: '2012-10-13', 204 | visits: 819 205 | }, { 206 | d: '2012-10-14', 207 | visits: 849 208 | }, { 209 | d: '2012-10-15', 210 | visits: 870 211 | }, { 212 | d: '2012-10-16', 213 | visits: 1063 214 | }, { 215 | d: '2012-10-17', 216 | visits: 1192 217 | }, { 218 | d: '2012-10-18', 219 | visits: 1224 220 | }, { 221 | d: '2012-10-19', 222 | visits: 1329 223 | }, { 224 | d: '2012-10-20', 225 | visits: 1329 226 | }, { 227 | d: '2012-10-21', 228 | visits: 1239 229 | }, { 230 | d: '2012-10-22', 231 | visits: 1190 232 | }, { 233 | d: '2012-10-23', 234 | visits: 1312 235 | }, { 236 | d: '2012-10-24', 237 | visits: 1293 238 | }, { 239 | d: '2012-10-25', 240 | visits: 1283 241 | }, { 242 | d: '2012-10-26', 243 | visits: 1248 244 | }, { 245 | d: '2012-10-27', 246 | visits: 1323 247 | }, { 248 | d: '2012-10-28', 249 | visits: 1390 250 | }, { 251 | d: '2012-10-29', 252 | visits: 1420 253 | }, { 254 | d: '2012-10-30', 255 | visits: 1529 256 | }, { 257 | d: '2012-10-31', 258 | visits: 1892 259 | },], 260 | // The name of the data record attribute that contains x-visitss. 261 | xkey: 'd', 262 | // A list of names of data record attributes that contain y-visitss. 263 | ykeys: ['visits'], 264 | // Labels for the ykeys -- will be displayed when you hover over the 265 | // chart. 266 | labels: ['Visits'], 267 | // Disables line smoothing 268 | smooth: false, 269 | resize: true 270 | }); 271 | 272 | // Bar Chart 273 | Morris.Bar({ 274 | element: 'morris-bar-chart', 275 | data: [{ 276 | device: 'iPhone', 277 | geekbench: 136 278 | }, { 279 | device: 'iPhone 3G', 280 | geekbench: 137 281 | }, { 282 | device: 'iPhone 3GS', 283 | geekbench: 275 284 | }, { 285 | device: 'iPhone 4', 286 | geekbench: 380 287 | }, { 288 | device: 'iPhone 4S', 289 | geekbench: 655 290 | }, { 291 | device: 'iPhone 5', 292 | geekbench: 1571 293 | }], 294 | xkey: 'device', 295 | ykeys: ['geekbench'], 296 | labels: ['Geekbench'], 297 | barRatio: 0.4, 298 | xLabelAngle: 35, 299 | hideHover: 'auto', 300 | resize: true 301 | }); 302 | 303 | 304 | }); 305 | -------------------------------------------------------------------------------- /static/mock/file.js: -------------------------------------------------------------------------------- 1 | Mock.mock(RegExp("/api/file" + "*"), "get", { 2 | "file": [ 3 | { 4 | "file_type": "dir", 5 | "mode": "755", 6 | "name": ".", 7 | "owner": "root" 8 | }, 9 | { 10 | "file_type": "dir", 11 | "mode": "755", 12 | "name": "..", 13 | "owner": "root" 14 | }, 15 | { 16 | "file_type": "dir", 17 | "mode": "755", 18 | "name": "bin", 19 | "owner": "root" 20 | }, 21 | { 22 | "file_type": "dir", 23 | "mode": "755", 24 | "name": "boot", 25 | "owner": "root" 26 | }, 27 | { 28 | "file_type": "dir", 29 | "mode": "755", 30 | "name": "CONFIG_DIR", 31 | "owner": "root" 32 | }, 33 | { 34 | "file_type": "dir", 35 | "mode": "755", 36 | "name": "dev", 37 | "owner": "root" 38 | }, 39 | { 40 | "file_type": "dir", 41 | "mode": "755", 42 | "name": "DOWNLOAD_DIR", 43 | "owner": "root" 44 | }, 45 | { 46 | "file_type": "dir", 47 | "mode": "755", 48 | "name": "etc", 49 | "owner": "root" 50 | }, 51 | { 52 | "file_type": "dir", 53 | "mode": "755", 54 | "name": "home", 55 | "owner": "root" 56 | }, 57 | { 58 | "file_type": "nonefile", 59 | "mode": "777", 60 | "name": "boot/initrd.img-4.15.0-99-generic", 61 | "owner": "root" 62 | }, 63 | { 64 | "file_type": "nonefile", 65 | "mode": "777", 66 | "name": "boot/initrd.img-4.15.0-96-generic", 67 | "owner": "root" 68 | }, 69 | { 70 | "file_type": "dir", 71 | "mode": "755", 72 | "name": "lib", 73 | "owner": "root" 74 | }, 75 | { 76 | "file_type": "dir", 77 | "mode": "755", 78 | "name": "lib64", 79 | "owner": "root" 80 | }, 81 | { 82 | "file_type": "dir", 83 | "mode": "700", 84 | "name": "lost+found", 85 | "owner": "root" 86 | }, 87 | { 88 | "file_type": "dir", 89 | "mode": "755", 90 | "name": "media", 91 | "owner": "root" 92 | }, 93 | { 94 | "file_type": "dir", 95 | "mode": "755", 96 | "name": "mnt", 97 | "owner": "root" 98 | }, 99 | { 100 | "file_type": "dir", 101 | "mode": "755", 102 | "name": "opt", 103 | "owner": "root" 104 | }, 105 | { 106 | "file_type": "dir", 107 | "mode": "555", 108 | "name": "proc", 109 | "owner": "root" 110 | }, 111 | { 112 | "file_type": "dir", 113 | "mode": "700", 114 | "name": "root", 115 | "owner": "root" 116 | }, 117 | { 118 | "file_type": "dir", 119 | "mode": "755", 120 | "name": "run", 121 | "owner": "root" 122 | }, 123 | { 124 | "file_type": "dir", 125 | "mode": "755", 126 | "name": "sbin", 127 | "owner": "root" 128 | }, 129 | { 130 | "file_type": "dir", 131 | "mode": "755", 132 | "name": "srv", 133 | "owner": "root" 134 | }, 135 | { 136 | "file_type": "nonefile", 137 | "mode": "600", 138 | "name": "swapfile", 139 | "owner": "root" 140 | }, 141 | { 142 | "file_type": "dir", 143 | "mode": "555", 144 | "name": "sys", 145 | "owner": "root" 146 | }, 147 | { 148 | "file_type": "dir", 149 | "mode": "776", 150 | "name": "tmp", 151 | "owner": "root" 152 | }, 153 | { 154 | "file_type": "dir", 155 | "mode": "755", 156 | "name": "usr", 157 | "owner": "root" 158 | }, 159 | { 160 | "file_type": "dir", 161 | "mode": "755", 162 | "name": "var", 163 | "owner": "root" 164 | }, 165 | { 166 | "file_type": "nonefile", 167 | "mode": "777", 168 | "name": "boot/vmlinuz-4.15.0-99-generic", 169 | "owner": "root" 170 | }, 171 | { 172 | "file_type": "nonefile", 173 | "mode": "777", 174 | "name": "boot/vmlinuz-4.15.0-96-generic", 175 | "owner": "root" 176 | }] 177 | }) -------------------------------------------------------------------------------- /static/mock/index.js: -------------------------------------------------------------------------------- 1 | Mock.mock("/api/sysinfo", { 2 | "cpu_info": { 3 | "core_num": 2, 4 | "cpu_num": 1, 5 | "model_name": "Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz", 6 | "processor_num": 2 7 | }, 8 | "disk_info": { 9 | "Total": 39, 10 | "Used": 29 11 | }, 12 | "io_info": { 13 | "io_recv": 0, 14 | "io_sent": 0, 15 | "net_io_recv": 0, 16 | "net_io_sent": 0 17 | }, 18 | "mem_info": { 19 | "Active": 2.71, 20 | "Buffers": 0.3, 21 | "Cached": 0.75, 22 | "MemAvailable": 1.4, 23 | "MemFree": 0.51, 24 | "MemTotal": 3.85, 25 | "SwapCached": 0, 26 | "Used": 2.29 27 | } 28 | }) -------------------------------------------------------------------------------- /static/mock/nginx.js: -------------------------------------------------------------------------------- 1 | Mock.mock(RegExp("/api/nginx" + "*"), "get", { 2 | "result": [ 3 | { 4 | "name": "startbootstrap-sb", 5 | "port": 8030 6 | }, { 7 | "name": "startbootstrap-sb", 8 | "port": 8030 9 | }, { 10 | "name": "startbootstrap-sb", 11 | "port": 8030 12 | }, { 13 | "name": "startbootstrap-sb", 14 | "port": 8030 15 | }, { 16 | "name": "startbootstrap-sb", 17 | "port": 8030 18 | }, { 19 | "name": "startbootstrap-sb", 20 | "port": 8030 21 | }, { 22 | "name": "startbootstrap-sb", 23 | "port": 8030 24 | }, 25 | ] 26 | }) -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunibyo-wly/LinuxControlPanel/da8c8945bfc67403e7f0eed63174fc39638f8013/test/__init__.py -------------------------------------------------------------------------------- /test/test_docker_manager.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | 4 | class TestDockerManager(TestCase): 5 | def test_get_container(self): 6 | self.assertEqual(3, 3) 7 | 8 | def test_stop_container(self): 9 | self.assertEqual(3, 3) 10 | 11 | def test_restart_container(self): 12 | self.assertEqual(3, 3) 13 | 14 | def test_delete_container(self): 15 | self.assertEqual(3, 3) 16 | -------------------------------------------------------------------------------- /test/test_file_manager.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest import TestCase 3 | 4 | 5 | class TestFileManager(TestCase): 6 | def test__change_mode_format(self): 7 | self.assertEqual(3, 3) 8 | 9 | def test_get_file_list(self): 10 | self.assertEqual(3, 3) 11 | 12 | def test_delete_file(self): 13 | self.assertEqual(3, 3) 14 | 15 | def test_upload(self): 16 | self.assertEqual(3, 3) 17 | -------------------------------------------------------------------------------- /test/test_system_info.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | def add(a, b): 5 | return a + b 6 | 7 | 8 | def minus(a, b): 9 | return a - b 10 | 11 | 12 | def multi(a, b): 13 | return a * b 14 | 15 | 16 | def divide(a, b): 17 | return a / b 18 | 19 | 20 | class TestSystemInfo(unittest.TestCase): 21 | 22 | def test_add(self): 23 | """Test method add(a, b)""" 24 | self.assertEqual(3, add(1, 2)) 25 | self.assertNotEqual(3, add(2, 2)) 26 | 27 | def test_minus(self): 28 | """Test method minus(a, b)""" 29 | self.assertEqual(1, minus(3, 2)) 30 | 31 | def test_multi(self): 32 | """Test method multi(a, b)""" 33 | self.assertEqual(6, multi(2, 3)) 34 | 35 | def test_divide(self): 36 | """Test method divide(a, b)""" 37 | self.assertEqual(2, divide(6, 3)) 38 | self.assertEqual(2.5, divide(5, 2)) 39 | --------------------------------------------------------------------------------