├── Procfile ├── README.md ├── app.json ├── app.py ├── function.py ├── requirements.txt ├── runtime.txt ├── static └── style.css └── templates ├── .DS_Store ├── account.html ├── base.html ├── index.html ├── list.html ├── login.html └── vm.html /Procfile: -------------------------------------------------------------------------------- 1 | web gunicorn app:app 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Azure-manager 2 | ### Manage your VM in Azure(multi users). 3 | ### 管理你在AZURE的机器 多用户 4 | 5 | 6 | 更新中版本会遇到一些BUG,欢迎提issues一起探讨,相对稳定版本见预发布版 7 | 8 | https://github.com/testnobody/azure-manager/tree/V0.61 9 | ## 10 | ### 8.8日更新: 11 | 更新数据库存放位置为当前目录下的flask_app.db,修复windows下部署时数据库链接失败的问题。先前部署的linux用户如要更新可以将/tmp/flask_app.db复制到项目当前目录即可继续使用 12 | ### 8.7日更新: 13 | 新增订阅状态查询 14 | ### 8.4日更新: 15 | 新增b1ls机型选择,硬盘默认64GB【可以选择30-1024GB】; 16 | 更新创建VM时手动更改默认账号密码,创建后更为安全。 17 | ### 8.2日更新: 18 | 新增windows机器选择(2012-2019),硬盘默认64GB,建议选择2012/2016配置 19 | 修复了一个身份验证BUG,增加安全性,但是还是很简陋,尽量保护好自己的管理网址和登录管理信息哦。 20 | ## 21 | ### Default VM infomation: 22 | ### 开出来的机器默认登录密码 23 | windows/linux通用默认账号密码 24 | USERNAME : defaultuser
25 | PASSWORD : Thisisyour.password1 26 | 27 | linux默认root密码 28 | USERNAME : root
29 | PASSWORD : rootpassword1 30 | ## 31 | ### 1.Install Python 3.9.4 32 | ### 安装python 3.9.4 33 | 34 | ### 2.Install Python Library 35 | ### 安装依赖包 36 | pip install -r requirements.txt 37 | 38 | ### 3.Set your Secret Key 39 | ### 随便设置一个密钥(cookie相关,13行) 40 | Set random string in app.py(line 13) 41 | 42 | ### 设置面板管理密码(app.py 7-8行;默认账号密码:admin admin123) 43 | Set admin password in app.py(line 7-8) 44 | 45 | ### 4.RUN!! 运行 46 | python app.py 47 | 48 | Visit 127.0.0.1:5000 and enjoy yourself. 49 | 50 | ### 5.Or you can deploy in heroku!! 51 | ### 或者你可以忽略以上所有步骤,一键部署到heroku 52 | A simple Python example application that's ready to run on Heroku. 53 | 54 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) 55 | ## 56 | ### notice1.面板登录的身份验证有点简陋,担心安全性的在公网慎用哦 57 | 58 | ### notice2. Azure key 获取方法 59 | 登录AZURE,在portal.azure.com右上角选择命令行bash 输入 az ad sp create-for-rbac --role contributor --name 【随机字符】 60 | 61 | 62 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "azure-manager", 3 | "description": "A azure-manager on Heroku", 4 | "keywords": [ 5 | "azure", 6 | "python", 7 | "Heroku" 8 | ], 9 | "repository": "https://github.com/testnobody/azure-manager", 10 | "env":{"adminname": { 11 | "description": "面板账户", 12 | "value": "admin" 13 | }, 14 | "adminpassword": { 15 | "description": "面板密码", 16 | "value": "admin" 17 | }, 18 | "random_key": { 19 | "description": "随便输入几个字符", 20 | "value": "rhay21qwjada" 21 | }}, 22 | "scripts": { 23 | "postdeploy": "python -c 'from app import db; db.create_all()'" 24 | }, 25 | "addons": [ 26 | "heroku-postgresql" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from flask import Flask, render_template, request, url_for, redirect, flash, make_response,session,jsonify 3 | import function 4 | from flask_sqlalchemy import SQLAlchemy 5 | import os 6 | import re 7 | 8 | defaultadmin ='admin' 9 | defaultpass = 'admin123' 10 | 11 | app = Flask(__name__) 12 | app.jinja_env.filters['zip'] = zip 13 | # 请将 xxx 替换为随机字符 14 | app.config['SECRET_KEY'] = os.environ.get('random_key','c2jf932hibfiuebvwievubheriuvberv') 15 | 16 | 17 | DATABASE_URL = os.environ.get('DATABASE_URL', 'sqlite:///flask_app.db') 18 | DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql://", 1) 19 | app.config['SQLALCHEMY_DATABASE_URI'] = DATABASE_URL 20 | db = SQLAlchemy(app) 21 | 22 | 23 | 24 | class User(db.Model): 25 | 26 | account = db.Column(db.String(100),primary_key=True) 27 | client_id = db.Column(db.String(100)) 28 | client_secret = db.Column(db.String(100)) 29 | tenant_id = db.Column(db.String(100)) 30 | subscription_id = db.Column(db.String(100)) 31 | 32 | def __init__(self, account, client_id,client_secret,tenant_id,subscription_id): 33 | self.account = account 34 | self.client_id = client_id 35 | self.client_secret = client_secret 36 | self.tenant_id = tenant_id 37 | self.subscription_id = subscription_id 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | @app.route('/login', methods=['GET', 'POST']) 46 | def login(): 47 | if request.method == 'POST': 48 | #logger.debug("login post method") 49 | 50 | username = request.form['userName'] 51 | password = request.form['passWord'] 52 | 53 | if username == os.environ.get('adminname',defaultadmin) and password == os.environ.get('adminpassword',defaultpass): 54 | #if username == 'adminusername' and password == 'adminpassword': 55 | 56 | session['username'] = username 57 | session['password'] = password 58 | resp = make_response(render_template('index.html', users=User.query.all())) 59 | resp.set_cookie('username', username) 60 | return resp 61 | #return jsonify({'status': '0', 'errmsg': 'login success!'}) 62 | else: 63 | # return redirect(url_for('error')) 64 | return jsonify({'status': '-1', 'errmsg': 'error account!'}) 65 | 66 | #logger.debug("login get method") 67 | return render_template('login.html') 68 | 69 | 70 | @app.route('/', methods=['GET', 'POST']) 71 | def index(): 72 | #logger.debug("index page") 73 | #logger.debug("cookie name %s" % request.cookies.get('username')) 74 | 75 | if 'username' in session: 76 | #logger.debug("login user is %s" % flask_login.current_user) 77 | #logger.debug('Logged in as %s' % escape(session['username'])) 78 | return render_template('index.html', users=User.query.all()) 79 | else: 80 | #logger.debug("you are not logged in") 81 | return redirect(url_for('login')) 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | @app.route('/account/add', methods=['GET', 'POST']) 93 | def accountadd(): 94 | if 'username' in session: 95 | if request.method == 'POST': # 判断是否是 POST 请求 96 | # 获取表单数据 97 | account = request.form.get('account') 98 | client_id = request.form.get('client_id') 99 | client_secret = request.form.get('client_secret') 100 | tenant_id = request.form.get('tenant_id') 101 | subscription_id = request.form.get('subscription_id') 102 | # 验证数据 103 | if not account or not client_id or not client_secret or not tenant_id or not subscription_id: 104 | flash('输入错误') # 显示错误提示 105 | return redirect(url_for('index')) # 重定向回主页 106 | # 保存表单数据到cookie 107 | u = User(account, client_id,client_secret,tenant_id,subscription_id) 108 | db.session.add(u) 109 | db.session.commit() 110 | 111 | resp = make_response(redirect(url_for('index'))) 112 | 113 | flash('添加管理账户成功') 114 | return resp 115 | 116 | return render_template('account.html') 117 | else: 118 | #logger.debug("you are not logged in") 119 | return redirect(url_for('login')) 120 | 121 | @app.route('/account/delete') 122 | def accountdel(): 123 | if 'username' in session: 124 | account = request.args.get('account') 125 | 126 | 127 | dele = User.query.filter(User.account == account).first() 128 | db.session.delete(dele) 129 | db.session.commit() 130 | 131 | 132 | 133 | 134 | 135 | flash('删除账户成功') 136 | resp = make_response(redirect(url_for('index'))) 137 | return resp 138 | else: 139 | #logger.debug("you are not logged in") 140 | return redirect(url_for('login')) 141 | 142 | 143 | @app.route('/account/list') 144 | def list(): 145 | if 'username' in session: 146 | account = request.args.get('account') 147 | result = User.query.filter(User.account == account).all() 148 | 149 | client_id = result[0].client_id 150 | client_secret = result[0].client_secret 151 | tenant_id = result[0].tenant_id 152 | subscription_id = result[0].subscription_id 153 | 154 | credential = function.create_credential_object(tenant_id, client_id, client_secret) 155 | 156 | dict, subscription_list = function.list(subscription_id, credential) 157 | return render_template('list.html',dict=dict, subscription_list=subscription_list, account=account) 158 | else: 159 | #logger.debug("you are not logged in") 160 | return redirect(url_for('login')) 161 | 162 | 163 | @app.route('/account/vm/create', methods=['GET', 'POST']) 164 | def create_vm(): 165 | if 'username' in session: 166 | account = request.args.get('account') 167 | print(account) 168 | 169 | if request.method == 'POST': 170 | result = User.query.filter(User.account == account).all() 171 | 172 | client_id = result[0].client_id 173 | client_secret = result[0].client_secret 174 | tenant_id = result[0].tenant_id 175 | subscription_id = result[0].subscription_id 176 | 177 | 178 | 179 | credential = function.create_credential_object(tenant_id, client_id, client_secret) 180 | tag = request.form.get('tag') 181 | location = request.form.get('location') 182 | size = request.form.get('size') 183 | os = request.form.get('os') 184 | set = request.form.get('set') 185 | rootpwd=request.form.get('rootpwd') 186 | storgesize=request.form.get('storgesize') 187 | ## 此处为VM默认登陆密码 188 | 189 | username = request.form.get('vmusername')#"defaultuser" 190 | password = request.form.get('vmpasswd')#"Thisisyour.password1" 191 | 192 | char = re.findall(r'[a-z]', password) 193 | bigchar = re.findall(r'[A-Z]', password) 194 | num = re.findall(r'[0-9]', password) 195 | if len(num) * len(bigchar) * len(char) == 0 or len(password) < 12 or len(password) > 72: 196 | flash('VM密码不合规,请输入至少12位密码,且包含大小写字母和数字') 197 | else: 198 | for i in range(int(set)): 199 | name = (tag + str(i + 1)) 200 | function.create_resource_group(subscription_id, credential, name, location) 201 | threading.Thread(target=function.create_or_update_vm, args=( 202 | subscription_id, credential, name, location, username, password, size, os,rootpwd,storgesize)).start() 203 | flash('创建中,请耐心等待VM创建完成,大约需要1-3分钟') 204 | 205 | 206 | 207 | return render_template('vm.html', account=account) 208 | else: 209 | #logger.debug("you are not logged in") 210 | return redirect(url_for('login')) 211 | 212 | 213 | @app.route('/account/vm/delete/') 214 | def delete_vm(tag): 215 | if 'username' in session: 216 | account = request.args.get('account') 217 | result = User.query.filter(User.account == account).all() 218 | 219 | client_id = result[0].client_id 220 | client_secret = result[0].client_secret 221 | tenant_id = result[0].tenant_id 222 | subscription_id = result[0].subscription_id 223 | 224 | 225 | 226 | 227 | 228 | credential = function.create_credential_object(tenant_id, client_id, client_secret) 229 | threading.Thread(target=function.delete_vm, args=(subscription_id, credential, tag)).start() 230 | flash("删除中,请耐心等待1-3分钟") 231 | 232 | dict, subscription_list = function.list(subscription_id, credential) 233 | return render_template('list.html', dict=dict, subscription_list=subscription_list, account=account) 234 | else: 235 | #logger.debug("you are not logged in") 236 | return redirect(url_for('login')) 237 | 238 | @app.route('/account/vm/start/') 239 | def start_vm(tag): 240 | if 'username' in session: 241 | account = request.args.get('account') 242 | 243 | result = User.query.filter(User.account == account).all() 244 | 245 | client_id = result[0].client_id 246 | client_secret = result[0].client_secret 247 | tenant_id = result[0].tenant_id 248 | subscription_id = result[0].subscription_id 249 | 250 | credential = function.create_credential_object(tenant_id, client_id, client_secret) 251 | threading.Thread(target=function.start_vm, args=(subscription_id, credential, tag)).start() 252 | flash("开机中,请耐心等待1-3分钟") 253 | dict, subscription_list = function.list(subscription_id, credential) 254 | return render_template('list.html', dict=dict, subscription_list=subscription_list, account=account) 255 | else: 256 | #logger.debug("you are not logged in") 257 | return redirect(url_for('login')) 258 | 259 | @app.route('/account/vm/stop/') 260 | def stop_vm(tag): 261 | if 'username' in session: 262 | account = request.args.get('account') 263 | 264 | result = User.query.filter(User.account == account).all() 265 | 266 | client_id = result[0].client_id 267 | client_secret = result[0].client_secret 268 | tenant_id = result[0].tenant_id 269 | subscription_id = result[0].subscription_id 270 | 271 | 272 | credential = function.create_credential_object(tenant_id, client_id, client_secret) 273 | threading.Thread(target=function.stop_vm, args=(subscription_id, credential, tag)).start() 274 | flash("关机中,请耐心等待1-3分钟") 275 | dict, subscription_list = function.list(subscription_id, credential) 276 | return render_template('list.html', dict=dict, subscription_list=subscription_list, account=account) 277 | else: 278 | #logger.debug("you are not logged in") 279 | return redirect(url_for('login')) 280 | 281 | @app.route('/account/vm/changeip/') 282 | def changeip_vm(tag): 283 | if 'username' in session: 284 | account = request.args.get('account') 285 | 286 | result = User.query.filter(User.account == account).all() 287 | 288 | client_id = result[0].client_id 289 | client_secret = result[0].client_secret 290 | tenant_id = result[0].tenant_id 291 | subscription_id = result[0].subscription_id 292 | 293 | 294 | credential = function.create_credential_object(tenant_id, client_id, client_secret) 295 | try: 296 | threading.Thread(target=function.change_ip, args=(subscription_id, credential, tag)).start() 297 | flash("更换IP进行中,请耐心等待1-3分钟") 298 | return redirect(url_for('index')) 299 | except: 300 | flash("出现未知错误,请重试") 301 | else: 302 | #logger.debug("you are not logged in") 303 | return redirect(url_for('login')) 304 | 305 | if __name__ == '__main__': 306 | db.create_all() 307 | app.run() 308 | -------------------------------------------------------------------------------- /function.py: -------------------------------------------------------------------------------- 1 | # coding:utf8 2 | from azure.mgmt.resource import ResourceManagementClient 3 | from azure.mgmt.network import NetworkManagementClient 4 | from azure.mgmt.compute import ComputeManagementClient 5 | from azure.common.credentials import ServicePrincipalCredentials 6 | import json 7 | 8 | from azure.mgmt.subscription import SubscriptionClient 9 | import time, base64 10 | 11 | 12 | def create_credential_object(tenant_id, client_id, client_secret): 13 | print("Create Credential Object") 14 | tenant_id = tenant_id 15 | client_id = client_id 16 | client_secret = client_secret 17 | print(tenant_id," ",client_id," ",client_secret) 18 | credential = ServicePrincipalCredentials(tenant=tenant_id, client_id=client_id, secret=client_secret) 19 | return credential 20 | 21 | 22 | def create_resource_group(subscription_id, credential, tag, location): 23 | print("Create eEsource Group") 24 | credential = credential 25 | resource_client = ResourceManagementClient(credential, subscription_id) 26 | RESOURCE_GROUP_NAME = tag 27 | LOCATION = location 28 | rg_result = resource_client.resource_groups.create_or_update(RESOURCE_GROUP_NAME, 29 | { 30 | "location": LOCATION 31 | } 32 | ) 33 | 34 | 35 | def create_or_update_vm(subscription_id, credential, tag, location, username, password, size, os, rootpwd, storgesize): 36 | global publisher, offer, sku 37 | compute_client = ComputeManagementClient(credential, subscription_id) 38 | RESOURCE_GROUP_NAME = tag 39 | VNET_NAME = ("vnet-" + tag) 40 | SUBNET_NAME = ("subnet-" + tag) 41 | IP_NAME = ("ip-" + tag) 42 | IP_CONFIG_NAME = ("ipconfig-" + tag) 43 | NIC_NAME = ("nicname-" + tag) 44 | LOCATION = location 45 | VM_NAME = tag 46 | USERNAME = username 47 | PASSWORD = password 48 | ROOT_PWD = rootpwd 49 | SIZE = size 50 | STORGESIZE = int(storgesize) 51 | if os == "debian11": 52 | publisher = "Debian" 53 | offer = "debian-11" 54 | sku = "11-gen2" 55 | version = "latest" 56 | elif os == "ubuntu20": 57 | publisher = "Canonical" 58 | offer = "0001-com-ubuntu-server-focal" 59 | sku = "20_04-lts-gen2" 60 | version = "latest" 61 | elif os == "ubuntu18": 62 | publisher = "Canonical" 63 | offer = "UbuntuServer" 64 | sku = "18.04-LTS" 65 | version = "latest" 66 | elif os == "ubuntu16": 67 | publisher = "Canonical" 68 | offer = "UbuntuServer" 69 | sku = "16.04.0-LTS" 70 | version = "latest" 71 | elif os == "centos": 72 | publisher = "OpenLogic" 73 | offer = "CentOS" 74 | sku = "7.5" 75 | version = "latest" 76 | elif os == "debian10": 77 | publisher = "Debian" 78 | offer = "debian-10" 79 | sku = "10" 80 | version = "latest" 81 | elif os == "windows2019": 82 | publisher = "MicrosoftWindowsServer" 83 | offer = "WindowsServer" 84 | sku = "2019-Datacenter-smalldisk" 85 | version = "latest" 86 | elif os == "windows2016": 87 | publisher = "MicrosoftWindowsServer" 88 | offer = "WindowsServer" 89 | sku = "2016-Datacenter-smalldisk" 90 | version = "latest" 91 | elif os == "windows2012": 92 | publisher = "MicrosoftWindowsServer" 93 | offer = "WindowsServer" 94 | sku = "2012-Datacenter-smalldisk" 95 | version = "latest" 96 | elif os == "windows-server-2012-r2-datacenter-smalldisk-g2": 97 | publisher = "MicrosoftWindowsServer" 98 | offer = "WindowsServer" 99 | sku = "2012-r2-datacenter-smalldisk-g2" 100 | version = "latest" 101 | else: 102 | publisher = "Canonical" 103 | offer = "UbuntuServer" 104 | sku = "18.04-LTS" 105 | version = "latest" 106 | 107 | network_client = NetworkManagementClient(credential, subscription_id) 108 | print("Create VNET") 109 | poller = network_client.virtual_networks.create_or_update(RESOURCE_GROUP_NAME, 110 | VNET_NAME, 111 | { 112 | "location": LOCATION, 113 | "address_space": { 114 | "address_prefixes": ["10.0.0.0/16"] 115 | } 116 | } 117 | ) 118 | vnet_result = poller.result() 119 | print("Create Subnets") 120 | poller = network_client.subnets.create_or_update(RESOURCE_GROUP_NAME, 121 | VNET_NAME, SUBNET_NAME, 122 | {"address_prefix": "10.0.0.0/24"} 123 | ) 124 | subnet_result = poller.result() 125 | print("Create IP") 126 | poller = network_client.public_ip_addresses.create_or_update(RESOURCE_GROUP_NAME, 127 | IP_NAME, 128 | { 129 | "location": LOCATION, 130 | "sku": {"name": "Basic"}, 131 | "public_ip_allocation_method": "Dynamic", 132 | "public_ip_address_version": "IPV4" 133 | } 134 | ) 135 | ip_address_result = poller.result() 136 | print("Create Network Interfaces") 137 | poller = network_client.network_interfaces.create_or_update(RESOURCE_GROUP_NAME, 138 | NIC_NAME, 139 | { 140 | "location": LOCATION, 141 | "ip_configurations": [{ 142 | "name": IP_CONFIG_NAME, 143 | "subnet": {"id": subnet_result.id}, 144 | "public_ip_address": { 145 | "id": ip_address_result.id} 146 | }], 147 | } 148 | ) 149 | nic_result = poller.result() 150 | s = "IyEvYmluL2Jhc2gKZWNobyByb290OnBweHdvMTIzIHxzdWRvIGNocGFzc3dkIHJvb3QKc3VkbyBzZWQgLWkgJ3MvXi4qUGVybWl0Um9vdExvZ2luLiovUGVybWl0Um9vdExvZ2luIHllcy9nJyAvZXRjL3NzaC9zc2hkX2NvbmZpZzsKc3VkbyBzZWQgLWkgJ3MvXi4qUGFzc3dvcmRBdXRoZW50aWNhdGlvbi4qL1Bhc3N3b3JkQXV0aGVudGljYXRpb24geWVzL2cnIC9ldGMvc3NoL3NzaGRfY29uZmlnOwpzdWRvIHNlcnZpY2Ugc3NoZCByZXN0YXJ0" 151 | if (ROOT_PWD != ""): 152 | d = base64.b64decode(s).decode('latin-1') 153 | d = d.replace("ppxwo123", ROOT_PWD) 154 | CUSTOM_DATA = base64.b64encode(d.encode("utf-8")).decode('latin-1') 155 | else: 156 | CUSTOM_DATA = s 157 | poller = compute_client.virtual_machines.create_or_update(RESOURCE_GROUP_NAME, VM_NAME, 158 | { 159 | "location": LOCATION, 160 | "storage_profile": { 161 | "osDisk": { 162 | "createOption": "fromImage", 163 | "diskSizeGB": STORGESIZE 164 | }, 165 | "image_reference": { 166 | "offer": offer, 167 | "publisher": publisher, 168 | "sku": sku, 169 | "version": version 170 | } 171 | }, 172 | "hardware_profile": { 173 | "vm_size": SIZE 174 | }, 175 | "os_profile": { 176 | "computer_name": VM_NAME, 177 | "admin_username": USERNAME, 178 | "admin_password": PASSWORD, 179 | "custom_data": CUSTOM_DATA 180 | }, 181 | "network_profile": { 182 | "network_interfaces": [{ 183 | "id": nic_result.id, 184 | }], 185 | } 186 | } 187 | ) 188 | vm_result = poller.result() 189 | print(vm_result) 190 | 191 | 192 | def start_vm(subscription_id, credential, tag): 193 | compute_client = ComputeManagementClient(credential, subscription_id) 194 | GROUP_NAME = tag 195 | VM_NAME = tag 196 | async_vm_start = compute_client.virtual_machines.start( 197 | GROUP_NAME, VM_NAME) 198 | async_vm_start.wait() 199 | 200 | 201 | def stop_vm(subscription_id, credential, tag): 202 | compute_client = ComputeManagementClient(credential, subscription_id) 203 | GROUP_NAME = tag 204 | VM_NAME = tag 205 | async_vm_deallocate = compute_client.virtual_machines.deallocate( 206 | GROUP_NAME, VM_NAME) 207 | async_vm_deallocate.wait() 208 | 209 | 210 | def delete_vm(subscription_id, credential, tag): 211 | resource_client = ResourceManagementClient(credential, subscription_id) 212 | GROUP_NAME = tag 213 | resource_client.resource_groups.delete(GROUP_NAME) 214 | 215 | 216 | def change_ip(subscription_id, credential, tag): 217 | compute_client = ComputeManagementClient(credential, subscription_id) 218 | GROUP_NAME = tag 219 | VM_NAME = tag 220 | async_vm_deallocate = compute_client.virtual_machines.deallocate( 221 | GROUP_NAME, VM_NAME) 222 | async_vm_deallocate.wait() 223 | time.sleep(10) 224 | async_vm_start = compute_client.virtual_machines.start( 225 | GROUP_NAME, VM_NAME) 226 | async_vm_start.wait() 227 | 228 | 229 | def list(subscription_id, credential): 230 | network_client = NetworkManagementClient(credential, subscription_id) 231 | info = network_client.public_ip_addresses.list_all() 232 | compute_client = ComputeManagementClient(credential, subscription_id) 233 | info2 = compute_client.virtual_machines.list_all() 234 | iplist = [] 235 | ipnames = [] 236 | 237 | for info in info: 238 | info = str(info) 239 | info = info.replace('"', '').replace('/', '').replace('None', '"None"').replace("'", '"').replace("<", 240 | '"').replace( 241 | ">", '"') 242 | info = json.loads(info) 243 | ipname = info["name"] 244 | ipname = ipname.replace('ip-', '') 245 | ipadd = info["ip_address"] 246 | iplist.append(ipadd) 247 | ipnames.append(ipname) 248 | 249 | for info2 in info2: 250 | info2 = str(info2) 251 | info2 = str(info2).replace("'", "").replace('"', "") 252 | info2 = info2.split(", ")[2].split(" ")[1] 253 | if info2 not in ipnames: 254 | ipnames.append(info2) 255 | iplist.append("None") 256 | dict = {"ip": iplist, "tag": ipnames} 257 | 258 | subscription_client = SubscriptionClient(credential) 259 | names = [] 260 | idstatus = [] 261 | for subscription in subscription_client.subscriptions.list(): 262 | names.append(subscription.display_name) 263 | idstatus.append(subscription.subscription_id + " " + str(subscription.state).replace('SubscriptionState.', '')) 264 | dic = {"name": names, "id_status": idstatus} 265 | 266 | 267 | return dict,dic 268 | 269 | 270 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | gunicorn==20.1.0 2 | adal==1.2.7 3 | psycopg2 4 | azure==4.0.0 5 | azure-applicationinsights==0.1.0 6 | azure-batch==4.1.3 7 | azure-common==1.1.27 8 | azure-cosmosdb-nspkg==2.0.2 9 | azure-cosmosdb-table==1.0.6 10 | azure-datalake-store==0.0.52 11 | azure-eventgrid==1.3.0 12 | azure-graphrbac==0.40.0 13 | azure-keyvault==1.1.0 14 | azure-loganalytics==0.1.0 15 | azure-mgmt==4.0.0 16 | azure-mgmt-advisor==1.0.1 17 | azure-mgmt-applicationinsights==0.1.1 18 | azure-mgmt-authorization==0.50.0 19 | azure-mgmt-batch==5.0.1 20 | azure-mgmt-batchai==2.0.0 21 | azure-mgmt-billing==0.2.0 22 | azure-mgmt-cdn==3.1.0 23 | azure-mgmt-cognitiveservices==3.0.0 24 | azure-mgmt-commerce==1.0.1 25 | azure-mgmt-compute==4.6.2 26 | azure-mgmt-consumption==2.0.0 27 | azure-mgmt-containerinstance==1.5.0 28 | azure-mgmt-containerregistry==2.8.0 29 | azure-mgmt-containerservice==4.4.0 30 | azure-mgmt-cosmosdb==0.4.1 31 | azure-mgmt-datafactory==0.6.0 32 | azure-mgmt-datalake-analytics==0.6.0 33 | azure-mgmt-datalake-nspkg==3.0.1 34 | azure-mgmt-datalake-store==0.5.0 35 | azure-mgmt-datamigration==1.0.0 36 | azure-mgmt-devspaces==0.1.0 37 | azure-mgmt-devtestlabs==2.2.0 38 | azure-mgmt-dns==2.1.0 39 | azure-mgmt-eventgrid==1.0.0 40 | azure-mgmt-eventhub==2.6.0 41 | azure-mgmt-hanaonazure==0.1.1 42 | azure-mgmt-iotcentral==0.1.0 43 | azure-mgmt-iothub==0.5.0 44 | azure-mgmt-iothubprovisioningservices==0.2.0 45 | azure-mgmt-keyvault==1.1.0 46 | azure-mgmt-loganalytics==0.2.0 47 | azure-mgmt-logic==3.0.0 48 | azure-mgmt-machinelearningcompute==0.4.1 49 | azure-mgmt-managementgroups==0.1.0 50 | azure-mgmt-managementpartner==0.1.1 51 | azure-mgmt-maps==0.1.0 52 | azure-mgmt-marketplaceordering==0.1.0 53 | azure-mgmt-media==1.0.0 54 | azure-mgmt-monitor==0.5.2 55 | azure-mgmt-msi==0.2.0 56 | azure-mgmt-network==2.7.0 57 | azure-mgmt-notificationhubs==2.1.0 58 | azure-mgmt-nspkg==3.0.2 59 | azure-mgmt-policyinsights==0.1.0 60 | azure-mgmt-powerbiembedded==2.0.0 61 | azure-mgmt-rdbms==1.9.0 62 | azure-mgmt-recoveryservices==0.3.0 63 | azure-mgmt-recoveryservicesbackup==0.3.0 64 | azure-mgmt-redis==5.0.0 65 | azure-mgmt-relay==0.1.0 66 | azure-mgmt-reservations==0.2.1 67 | azure-mgmt-resource==2.2.0 68 | azure-mgmt-scheduler==2.0.0 69 | azure-mgmt-search==2.1.0 70 | azure-mgmt-servicebus==0.5.3 71 | azure-mgmt-servicefabric==0.2.0 72 | azure-mgmt-signalr==0.1.1 73 | azure-mgmt-sql==0.9.1 74 | azure-mgmt-storage==2.0.0 75 | azure-mgmt-subscription==0.2.0 76 | azure-mgmt-trafficmanager==0.50.0 77 | azure-mgmt-web==0.35.0 78 | azure-nspkg==3.0.2 79 | azure-servicebus==0.21.1 80 | azure-servicefabric==6.3.0.0 81 | azure-servicemanagement-legacy==0.20.7 82 | azure-storage-blob==1.5.0 83 | azure-storage-common==1.4.2 84 | azure-storage-file==1.4.0 85 | azure-storage-queue==1.4.0 86 | certifi==2021.5.30 87 | cffi==1.14.5 88 | chardet==4.0.0 89 | click==8.0.1 90 | cryptography==3.4.7 91 | Flask==2.0.1 92 | Flask-SQLAlchemy==2.5.1 93 | greenlet==1.1.0 94 | idna==2.10 95 | isodate==0.6.0 96 | itsdangerous==2.0.1 97 | Jinja2==3.0.1 98 | MarkupSafe==2.0.1 99 | msrest==0.6.21 100 | msrestazure==0.6.4 101 | numpy==1.21.0 102 | oauthlib==3.1.1 103 | pandas==1.3.0 104 | pycparser==2.20 105 | PyJWT==2.1.0 106 | python-dateutil==2.8.1 107 | pytz==2021.1 108 | requests==2.25.1 109 | requests-oauthlib==1.3.0 110 | six==1.16.0 111 | SQLAlchemy==1.4.20 112 | urllib3==1.26.6 113 | Werkzeug==2.0.1 114 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.9.4 2 | -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | /* 页面整体 */ 2 | body { 3 | margin: auto; 4 | max-width: 580px; 5 | font-size: 14px; 6 | font-family: Helvetica, Arial, sans-serif; 7 | } 8 | 9 | /* 页脚 */ 10 | footer { 11 | color: #888; 12 | margin-top: 15px; 13 | text-align: center; 14 | padding: 10px; 15 | } 16 | 17 | /* 头像 */ 18 | .avatar { 19 | width: 40px; 20 | } 21 | 22 | .list { 23 | list-style-type: none; 24 | padding: 0; 25 | margin-bottom: 10px; 26 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12); 27 | } 28 | 29 | .list li { 30 | padding: 12px 24px; 31 | border-bottom: 1px solid #ddd; 32 | } 33 | 34 | .list li:last-child { 35 | border-bottom:none; 36 | } 37 | 38 | .list li:hover { 39 | background-color: #f8f9fa; 40 | } 41 | 42 | /* 覆盖某些浏览器对 input 元素定义的字体 */ 43 | input[type=submit] { 44 | font-family: inherit; 45 | } 46 | 47 | input[type=text] { 48 | border: 1px solid #ddd; 49 | } 50 | 51 | input[name=account] { 52 | width: 150px; 53 | } 54 | input[name=client_id] { 55 | width: 150px; 56 | } 57 | 58 | .btn { 59 | font-size: 12px; 60 | padding: 3px 5px; 61 | text-decoration: none; 62 | cursor: pointer; 63 | background-color: white; 64 | color: black; 65 | border: 1px solid #555555; 66 | border-radius: 5px; 67 | } 68 | 69 | .btn:hover { 70 | text-decoration: none; 71 | background-color: black; 72 | color: white; 73 | border: 1px solid black; 74 | } 75 | 76 | .alert { 77 | position: relative; 78 | padding: 7px; 79 | margin: 7px 0; 80 | border: 1px solid transparent; 81 | color: #004085; 82 | background-color: #cce5ff; 83 | border-color: #b8daff; 84 | border-radius: 5px; 85 | } 86 | 87 | .inline-form { 88 | display: inline; 89 | } 90 | nav ul { 91 | list-style-type: none; 92 | margin: 0; 93 | padding: 0; 94 | overflow: hidden; 95 | background-color: #333; 96 | } 97 | 98 | nav li { 99 | float: left; 100 | } 101 | 102 | nav li a { 103 | display: block; 104 | color: white; 105 | text-align: center; 106 | padding: 8px 12px; 107 | text-decoration: none; 108 | } 109 | 110 | nav li a:hover { 111 | background-color: #111; 112 | } 113 | 114 | .float-right { 115 | float: right; 116 | } 117 | 118 | .go { 119 | font-size: 12px; 120 | font-weight: bold; 121 | color: black; 122 | text-decoration: none; 123 | background: #F5C518; 124 | border-radius: 5px; 125 | padding: 3px 5px; 126 | } -------------------------------------------------------------------------------- /templates/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testnobody/azure-manager/1a0bae2e7d6189de31114282238861a2bd0e901e/templates/.DS_Store -------------------------------------------------------------------------------- /templates/account.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

添加待管理Azure账户

5 |
6 |

账户名称(任意)

7 |

Client_id

8 |

Client_secret

9 |

Tenant_id

10 |

Subscription_id

11 | 12 |
13 | {% endblock %} -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% block head %} 5 | 6 | 7 | Azure Manager 8 | 9 | 10 | {% endblock %} 11 | 12 | 13 | {% for message in get_flashed_messages() %} 14 |
{{ message }}
15 | {% endfor %} 16 |

17 | Azure Manager 18 |

19 | 26 | {% block content %}{% endblock %} 27 |
28 | 源仓库作者:@1injex
29 | © 2021 Powered by Flask 30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

账号管理中

5 | {% for u in users %} 6 | 14 | {% else %} 15 |

数据库中无账号,点击右上角添加吧.

16 | {% endfor %} 17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /templates/list.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

{{ account }} :订阅状态

5 |
    6 | {% for name, id_status in subscription_list["name"]|zip(subscription_list["id_status"]) %} 7 |
  • {{ name }}-{{ id_status }} 8 |
  • 9 | {% endfor %}{# 使用 endfor 标签结束 for 语句 #} 10 |
11 |

{{ account }} VM管理 新建虚拟机 刷新账户状态

12 | 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 登录 6 | 7 | 8 |

登录页面

9 |
10 | 用户名 11 | 密码 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /templates/vm.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

账户 {{ account }} 管理中 返回

5 |
6 |
7 |

8 |

9 |

10 |

11 |

23 |

37 |

45 |

52 |

64 | 65 |
66 |
67 | {% endblock %} 68 | --------------------------------------------------------------------------------