Commit 9e108256 authored by Wallen姚文辉's avatar Wallen姚文辉

一版

parents
mysql:
host: 127.0.0.1
port: 3306
db: galax_test_center
user: root
password: ywh940509
email:
user: wallen.ywh@galaxyoversea.com
password: ywh940509
host: smtp.exmail.qq.com
port: 465
\ No newline at end of file
This diff is collapsed.
from flask import Blueprint, request, jsonify,session
from manager.serverCenter import server,emailserver
from model.Model import User,Emails
import base64
import requests
from bs4 import BeautifulSoup
from manager.tools import sqlOrmToJson
db=server.db
tool = Blueprint("tools", __name__, url_prefix='/tool')
jiraAddress="http://jira.galaxy-immi.com"
def getheader(id_):
result=db.session.query(User.jira_account,User.jira_password).filter_by(id=id_).first()
db.session.close()
if not result:
return
headers={
"Authorization":"Basic "+base64.b64encode((result.jira_account+":"+result.jira_password).encode('utf-8')).decode(),
"accept":"application/json,text/javascript,*/*;q=0.01"
}
return headers
# def sendEmail(to,subject,contents=None,attachments=None):
# '''
# to 发送目标,数组时为多人
# subject 主题
# contents 内容
# attachments 附件,数组时为多个
# '''
# emailserver.server.send(to=to,subject=subject,contents=contents,attachments=attachments)
@tool.route('/testport/sendport', methods=["POST"])
def sendport():
import yagmail
data=request.json
user = 'wallen.ywh@galaxyoversea.com'
password = 'Ywh940509'
yag = yagmail.SMTP( user=user, password=password, host='smtp.exmail.qq.com',port=465)
print(emailserver.server.__dict__)
# emailserver.server.send(data["to"],data.get("subject"),data.get("contents"))
yag.send(data["to"],data.get("subject"),data.get("contents"))
return jsonify({"code": 200, "message": "请求成功"}),200
@tool.route('/testport/getproject', methods=["GET"])
def getproject():
headers=getheader(session.get("id"))
if not headers:
return jsonify({"code": 502, "message": "请完善jira信息"}),502
result=requests.request("get",jiraAddress+"/rest/api/1.0/menus/browse_link?inAdminMode=false",headers=headers).json()
# return jsonify({"code": 200, "message": "请求成功", "data": result}),200
res=[]
for each in result["sections"]:
if each.get("title")=="当前活动的项目" or each.get("title")=="您最近参与的项目":
for ele in each["items"]:
res.append({"key":ele["url"].split("/")[-1],"name":ele["label"]})
if res:
return jsonify({"code": 200, "message": "请求成功", "data": res}),200
else:
return jsonify({"code": 402, "message": "未找到相关项目,请确认jira信息是否正确"}),402
@tool.route('/testport/iteration/<getproject>', methods=["GET"])
def iteration(getproject):
headers=getheader(session.get("id"))
if not headers:
return jsonify({"code": 502, "message": "请完善jira信息"}),502
print(requests.request("get",jiraAddress+"/rest/agile/1.0/board?projectKeyOrId="+getproject,headers=headers).json())
id_=requests.request("get",jiraAddress+"/rest/agile/1.0/board?projectKeyOrId="+getproject,headers=headers).json().get("values")[0].get("id")
result=requests.request("get",jiraAddress+"/rest/greenhopper/1.0/xboard/plan/backlog/data.json?rapidViewId="+str(id_)+"&selectedProjectKey="+getproject,headers=headers).json()
res=[]
for each in result["sprints"]:
res.append({"key":each["id"],"name":each["name"]})
if res:
return jsonify({"code": 200, "message": "请求成功", "data": res}),200
else:
return jsonify({"code": 402, "message": "未找到相关项目,请确认jira信息是否正确"}),402
@tool.route('/testport/reportinfo', methods=["GET"])
def reportinfo():
project=request.args.get("project")
iteration=request.args.get("iteration")
headers=getheader(session.get("id"))
id_=requests.request("get",jiraAddress+"/rest/agile/1.0/board?projectKeyOrId="+project,headers=headers).json().get("values")[0].get("id")
result=requests.request("get",jiraAddress+"/rest/greenhopper/1.0/xboard/plan/backlog/data.json?rapidViewId="+str(id_)+"&selectedProjectKey="+project,headers=headers).json()
all_=result.get("issues")
status={}
for k,v in result["entityData"]["statuses"].items():
status[k]=v["statusName"]
priority={}
for k,v in result["entityData"]["priorities"].items():
priority[k]=v["priorityName"]
type_={}
for k,v in result["entityData"]["types"].items():
type_[str(k)]=v["typeName"]
spList=[]
sprint=None
for i in result["sprints"]:
if i["id"]==int(iteration):
spList=i["issuesIds"]
sprint=i["name"]
break
# x:type_[x["typeId"]]=="测试用例"
iterationitems=list(filter(lambda x:x["id"] in spList,all_))
peoples={'test':[],'qian':[],'hou':[],'chan':[]}
story=list(filter(lambda x:type_[x["typeId"]]=="Story",iterationitems))
bug=list(filter(lambda x:type_[x["typeId"]]=="BUG",iterationitems))
testcase=list(filter(lambda x:type_[x["typeId"]]=="测试用例",all_))
for i in story:
i["case"]=[]
i["childrenTask"]=[i.get("key")]
chan=i["extraFields"][0]["html"]
if chan not in peoples["chan"]:
peoples["chan"].append(chan)
i["peoples"]=[chan]
info=requests.request("get",jiraAddress+"/rest/greenhopper/1.0/xboard/issue/details.json?rapidViewId="+str(id_)+"&issueIdOrKey="+i["key"]+"&loadSubtasks=true",headers=headers).json()
for j in info.get("tabs").get("defaultTabs"):
if j.get("tabId")=="SUB_TASKS":
for m in j.get("subtaskEntries"):
k=None
if m.get("assignee"):
k=m.get("assignee").get("displayName")
if k not in i["peoples"]:
i["peoples"].append(k)
if (("测试" in m.get("summary") or "用例" in m.get("summary")) and k and k not in peoples["test"])and("前端" not in m.get("summary")) and ("后端" not in m.get("summary")) :
peoples["test"].append(k)
elif "前端" in m.get("summary") and k and k not in peoples["qian"]:
peoples["qian"].append(k)
elif "后端" in m.get("summary") and k and k not in peoples["hou"]:
peoples["hou"].append(k)
i["childrenTask"].append(m.get("key"))
elif j.get("tabId")=="THIRD_PARTY_TAB":
for each in j.get("sections"):
if each["label"]=="测试用例":
soup=BeautifulSoup(each["html"],"html.parser")
i["case"]=[i.next_element for i in soup.select('tr>td[align="left"]>span>a')]
res={"project":result["projects"][0]["name"],"sprint":sprint,"people":peoples,"story":story,"bug":bug,"testcase":testcase,"statusMap":status,"priorityMap":priority,"typeMap":type_}
return jsonify({"code": 200, "message": "请求成功", "data": res}),200
@tool.route('/testport/addemailuser', methods=["POST"])
def addemailuser():
data=request.json
addinfo=Emails(name=data.get("name"),address=data.get("address"))
db.session.add(addinfo)
db.session.commit()
db.session.close()
return jsonify({"code": 200, "message": "请求成功"}),200
@tool.route('/testport/deleteemailuser/<id_>', methods=["DELETE"])
def deleteemailuser(id_):
db.session.query(Emails).filter_by(id=id_).delete()
db.session.commit()
db.session.close()
return jsonify({"code": 200, "message": "请求成功"}),200
@tool.route('/testport/emailuserlist', methods=["GET"])
def emailuserlist():
data=db.session.query(Emails.id,Emails.name,Emails.address).all()
db.session.close()
return jsonify({"code": 200, "message": "请求成功","data": sqlOrmToJson(data)}),200
@tool.route('/testport/editemailuser', methods=["POST"])
def editemailuser():
db.session.query(Emails).filter_by(id=request.json.get("id")).update({"name":request.json.get("name"),"address":request.json.get("address")})
db.session.commit()
db.session.close()
return jsonify({"code": 200, "message": "请求成功"}),200
import os
from flask import Blueprint, request, jsonify,session
# from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from werkzeug.security import generate_password_hash,check_password_hash
from sqlalchemy import or_,func
from authlib.jose import jwt, JoseError
from model.Model import User,Role,Organization,Menu,Permissions,Element
from manager.serverCenter import server
from manager.tools import sqlOrmToJson
import time
db=server.db
user = Blueprint("users", __name__, url_prefix='/user')
SECRET_KEY = 'abcdefghijklmm'
import hashlib
# 生成token
def generate_auth_token(user_id, expiration=36000):
header = {'alg': 'HS256'}
data={'user_code': user_id}
return jwt.encode(header=header,payload=data,key=SECRET_KEY)
@user.route('/login', methods=["POST"])
def login():
'''
{
"name":用户名
"password":密码
}
'''
data = request.json
user = db.session.query(User).filter_by(name=data.get("name")).first()
if not user:
db.session.close()
return jsonify({"code": 501, "message": "用户不存在", "data": ""}),501
elif user.disable==1:
db.session.close()
return jsonify({"code": 502, "message": "账号被禁用"}),502
elif not check_password_hash(user.password,data.get("password")):
if user.try_time<5:
user.try_time +=1
else:
user.disable=1
db.session.commit()
disableCount=5-user.try_time
db.session.close()
return jsonify({"code": 405, "message": f"密码错误,请重试({disableCount}次后账号将被锁定)", "data": ""}),405
else:
user.token = generate_auth_token(user.id).decode()
user.try_time = 0
db.session.commit()
session["id"] = user.id
session["name"] = user.name
session['token'] = str(user.token)
datas = {'id': user.id, "name": user.name, "token": user.token}
db.session.commit()
db.session.close()
return jsonify({"code": 200, "message": "请求成功", "data": datas}),200
@user.route("/logout",methods=["POST"])
def logout():
session.clear()
return jsonify({"code": 200, "message": "请求成功"}),200
@user.route("/adduser",methods=["POST"])
def adduser():
'''
{
name
password
role_id
organization_id 组织id 非必传
disable 非必传
eamil 非必传
}
'''
data=request.json
if not data.get("name"):
return jsonify({"code": 401, "message": "用户名不能为空"}),401
if not data.get("password"):
return jsonify({"code": 401, "message": "密码不能为空"}),401
if not data.get("role_id"):
return jsonify({"code": 401, "message": "角色不能为空"}),401
if db.session.query(User).filter_by(name=data.get("name")).count()>1:
db.session.close()
return jsonify({"code": 401, "message": "用户名重复,请重试"}),401
create_time=time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))
db.session.add(User(name=data.get("name"),password=generate_password_hash(data.get("password")),role_id=int(data.get("role_id")),organization_id=(int(data.get("organization_id")) or None),create_date=create_time,create_user=(session.get("id") or 0),
disable=(data.get("disable") or 0),jira_account=data.get("jira_account"),jira_password=data.get("jira_password"),email_adress=(data.get("email_adress") or None)))
db.session.commit()
db.session.close()
return jsonify({"code": 200, "message": "添加成功"}),200
@user.route("/getuser/<id_>",methods=["GET"])
def getuser(id_):
res=sqlOrmToJson(db.session.query(User.id,User.name,User.role_id,User.organization_id,User.create_date,User.jira_account,User.jira_password,User.create_user,User.email_address).filter_by(id=int(id_)).first())
db.session.close()
return jsonify({"code": 200, "message": "请求成功","data":res}),200
@user.route("/updateuser",methods=["POST"])
def updateuser():
'''
id
password 非必传 改密码时可调用
'''
data=request.json
user_id=data.get("id")
if (user_id==session.get("id")):
if data.get("password"):
oldPass=generate_password_hash(data.get("oldpassword"))
if oldPass!=db.session.query(User.password).filter_by(id=user_id).first().password:
db.session.close
return jsonify({"code": 503, "message": "旧密码错误"}),503
data["password"]=generate_password_hash(data.get("password"))
del data['secondpassword']
del data['oldpassword']
try:
del data["create_date"]
except:
pass
# try:
userinfo=db.session.query(User).filter_by(id=user_id)
userinfo.update(data)
userinfo.try_time=0
db.session.commit()
db.session.close()
return jsonify({"code": 200, "message": "修改成功"})
# except:
# db.session.rollback()
# db.session.close()
# return jsonify({"code": 401, "message": "修改失败"}),401
@user.route("/user_list",methods=["GET"])
def finduser():
'''
page_size 页数
page_num 当前页
name
organizations 数组
roles 数组
'''
data=request.args
page_size = int(data.get("page_size"))
page_num = int(data.get("page_num"))
users=db.session.query(User.id,User.name,func.date_format(User.create_date, "%Y-%m-%d %H:%i:%s").label('create_date'),User.role_id,Role.name.label("role_name"),Organization.id.label("organization_id"),Organization.path).join(Role,Role.id==User.role_id).join(Organization,Organization.id==User.organization_id)
if data.get("name"):
users=users.filter(User.name.like("%"+data.get("name")+"%"))
if data.getlist('organizations[]'):
info=[]
for i in data.getlist('organizations[]'):
info.append(Organization.find_path.like("%-"+i+"-%"))
users=users.filter(or_(*info))
if data.getlist("roles[]"):
users=users.filter(Role.id.in_(data.getlist("roles[]")))
result=users.paginate(page=page_num, per_page=page_size)
total_sum = result.total
res = result.items
db.session.close()
return jsonify({"code": 200, "message": "请求成功", "data": {"rows": sqlOrmToJson(res), "total": total_sum}})
@user.route("/userdetail",methods=["GET"])
def userdetail():
userinfo=db.session.query(User.id,User.name,User.email_address,Role.name.label("role_name"),Organization.path.label("organization")).filter(User.role_id==Role.id,User.organization_id==Organization.id,User.id==session.get("id")).first()
print(userinfo.email_address)
usermenu=db.session.query(Menu.id,Menu.name,Menu.router,Menu.icon,Menu.parent_id,Menu.path).filter(Permissions.type==0,Permissions.p_id==Menu.id,Permissions.role_id==Role.id,User.role_id==Role.id,User.id==session.get("id")).all()
usermenu=sorted(usermenu,key=lambda x:len(list(filter(None,x.path.split("/")))),reverse=False)
result=[]
for each in usermenu:
target=result
info=list(filter(None,each.path.split("/")))
for path in range(len(info)):
breakinfo=False
for index in range(len(target)):
if target[index]["id"]==int(info[path]):
target=target[index]["children"]
breakinfo=True
break
if not breakinfo and path==len(info)-1:
target.append({"name":each.name,"router":each.router,"id":each.id,"icon":each.icon,"children":[]})
elif breakinfo:
continue
else:
break
target=result
db.session.close()
res=sqlOrmToJson(userinfo)
res["menu"]=result
return jsonify({"code": 200, "message": "请求成功", "data": res})
@user.route("/elements",methods=["GET"])
def elements():
id_=request.args.get("id")
res=db.session.query(Element.u_id).filter(User.id==session.get("id"),Role.id==User.role_id,Permissions.role_id==Role.id,Permissions.type==1,Permissions.p_id==Element.id,Element.menu_id==int(id_)).all()
result=[i.u_id for i in res]
db.session.close()
return jsonify({"code": 200, "message": "请求成功", "data": result})
from flask import Flask, request, jsonify, session, current_app
import os
from flask_sqlalchemy import SQLAlchemy
from datetime import timedelta
from datetime import datetime, date
from manager.tools import get_config
import yagmail
config=get_config()
mysqlConf=config["mysql"]
eamilConf=config["email"]
# redisConf=config["redis"]
# emailConf=config["email"]
# class CustomJSONEncoder(JSONEncoder):
# def default(self, obj):
# if isinstance(obj, datetime):
# return obj.strftime('%Y-%m-%d %H:%M:%S')
# elif isinstance(obj, date):
# return obj.strftime('%Y-%m-%d')
# else:
# return JSONEncoder.default(self, obj)
class server():
app = Flask(__name__, template_folder='auto_test/html', static_folder='')
app.config['SQLALCHEMY_DATABASE_URI'] = f"mysql+pymysql://{mysqlConf['user']}:{mysqlConf['password']}@{mysqlConf['host']}:{mysqlConf['port']}/{mysqlConf['db']}?charset=utf8mb4"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SECRET_KEY'] = os.urandom(24)
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)
app.secret_key = "test"
# app.json_encoder = CustomJSONEncoder
db = SQLAlchemy(app)
class emailserver():
server=yagmail.SMTP(user=eamilConf["user"], password=eamilConf["password"], host=eamilConf["host"],port=eamilConf["port"])
# def send(self,to,subject,contents=None,attachments=None):
# '''
# to 发送目标,数组时为多人
# contents 内容
# attachments 附件,数组时为多个
# '''
# emailserver.server.send()
import yaml
import os
# from manager.serverCenter import server
from sqlalchemy.engine.row import Row
curpath = os.path.dirname(os.path.realpath(__file__))
par_path = os.path.dirname(curpath)
class path():
configPath=par_path+"/config.yml"
def get_config():
with open(path.configPath,"r",encoding='utf-8') as f:
data=yaml.load(f,Loader=yaml.FullLoader)
return data
def getdata(info):
data={}
# print(info.keys())
for k,v in info.items():
if k != '_sa_instance_state':
data[k]=v
return data
def row_to_json(row):
if isinstance(row,Row):
return row._asdict()
else:
result=getdata(row)
return result
def sqlOrmToJson(data):
'''
sql 对象转 json字符串
'''
if isinstance(data,list):
result=[]
for i in data:
result.append(row_to_json(i))
return result
else:
return row_to_json(data)
import os
import sys
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
from manager.serverCenter import server
class Config(object):
JOBS = []
SCHEDULER_API_ENABLED = True
SCHEDULER_TIMEZONE = 'Asia/Shanghai'
SCHEDULER_JOBSTORES = {
'default': SQLAlchemyJobStore(url=server.app.config['SQLALCHEMY_DATABASE_URI'])
}
@server.app.before_request
def token():
token = request.headers.get("token")
release_path = ["/user/login", "/user/logout"]
next_=False
for i in release_path:
if i in request.path:
next_=True
break
if next_:
pass
elif token is None or session.get("id") is None:
db.session.close()
return jsonify({"code": 401, "message": "登陆已过期"}),401
elif db.session.query(User.token).filter_by(id=session.get("id")).first()[0] == token:
session['token']=token
db.session.close()
pass
else:
return jsonify({"code": 401, "message": "登陆已过期"}),401
if __name__ == '__main__':
rel_path = os.path.dirname(os.path.realpath(__file__))
if rel_path not in sys.path:
sys.path.append(rel_path)
server.app.config.from_object(Config())
from controller.User import *
server.app.register_blueprint(user)
from controller.Identity import *
server.app.register_blueprint(identity)
from controller.Tool import *
server.app.register_blueprint(tool)
server.app.run(host="0.0.0.0", port=8000, debug=True)
import sys
import os
project_path=os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(project_path)
from manager.serverCenter import server
from sqlalchemy import Column,Integer,String,Integer,String,DateTime,text,Text
db=server.db
class Menu(db.Model):
'''
菜单表
'''
__tablename__="menu"
__table_args__ = {'mysql_engine' : 'InnoDB', 'mysql_charset' : 'utf8mb4'}
id=Column(Integer, primary_key=True,autoincrement=True)
name=Column(String(64), nullable=False,comment='菜单名')
router=Column(String(64), nullable=True,comment='路由')
icon=Column(String(64), nullable=True,comment='菜单图标')
parent_id=Column(Integer, nullable=False,server_default=text("0"),comment='父菜单id。为0时表示顶级菜单')
path=Column(String(64), nullable=True,comment='寻址路径')
default_show=Column(Integer, nullable=False,server_default=text("0"),comment='默认所有角色均有该权限,1为是')
class Element(db.Model):
'''
菜单有权限的元素表
'''
__tablename__="element"
__table_args__ = {'mysql_engine' : 'InnoDB', 'mysql_charset' : 'utf8mb4'}
id=Column(Integer, primary_key=True,autoincrement=True)
menu_id=Column(Integer, nullable=False,comment='父菜单id')
u_id=Column(String(64), nullable=True,comment='元素uid,由前端提供')
name=Column(String(64), nullable=False,comment='元素名')
default_show=Column(Integer, nullable=False,server_default=text("0"),comment='默认是否展示,为0时表示不展示')
class Organization(db.Model):
'''
组表
'''
__tablename__="organization"
__table_args__ = {'mysql_engine' : 'InnoDB', 'mysql_charset' : 'utf8mb4'}
id=Column(Integer, primary_key=True,autoincrement=True)
name=Column(String(64), nullable=False,comment='组名')
parent_id=Column(Integer, nullable=False,server_default=text("0"),comment='父组织的id。为0时表示顶级菜单')
find_path=Column(String(64), nullable=False,comment='寻找路径')
path=Column(String(64), nullable=False,comment='组织全路径名')
class Role(db.Model):
'''
角色表
'''
__tablename__="role"
__table_args__ = {'mysql_engine' : 'InnoDB', 'mysql_charset' : 'utf8mb4'}
id=Column(Integer, primary_key=True,autoincrement=True)
name=Column(String(64), nullable=False,comment='角色名')
class User(db.Model):
'''
用户表
'''
__tablename__="user"
__table_args__ = {'mysql_engine' : 'InnoDB', 'mysql_charset' : 'utf8mb4'}
id=Column(Integer, primary_key=True,autoincrement=True)
name=Column(String(64), nullable=False,comment='用户名')
role_id=Column(Integer, nullable=False,comment='角色id')
organization_id=Column(Integer, nullable=True,comment='组织id')
create_date=Column(DateTime,nullable=True,comment="创建时间")
password=Column(Text(64), nullable=False,comment='密码')
disable=Column(Integer, nullable=False,server_default=text("0"),comment='受否禁用')
jira_account=Column(String(64), nullable=True,comment='jira登录账户')
jira_password=Column(String(64), nullable=True,comment='jira登录密码')
email_address=Column(String(64), nullable=True,comment='邮箱地址')
create_user=Column(Integer, nullable=False,comment='创建人')
try_time=Column(Integer, nullable=False,server_default=text("0"),comment='重试次数')
token=Column(Text(64), nullable=True,comment='登录token')
class Permissions(db.Model):
'''
权限表
'''
__tablename__="permissions"
__table_args__ = {'mysql_engine' : 'InnoDB', 'mysql_charset' : 'utf8mb4'}
id=Column(Integer, primary_key=True,autoincrement=True)
role_id=Column(String(64), nullable=False,comment='角色id')
type=Column(Integer, nullable=False,server_default=text("0"),comment='0为菜单权限,1为元素权限')
p_id=Column(Integer, nullable=False,comment='对应权限得id,0时为菜单得id,1时为元素的id')
status=Column(Integer, nullable=False,server_default=text("0"),comment='1true-0false')
update_date=Column(DateTime,nullable=False,comment="更新时间")
update_user=Column(Integer, nullable=False,comment='更新人')
class Emails(db.Model):
'''
角色表
'''
__tablename__="emails"
__table_args__ = {'mysql_engine' : 'InnoDB', 'mysql_charset' : 'utf8mb4'}
id=Column(Integer, primary_key=True,autoincrement=True)
name=Column(String(64), nullable=False,comment='邮件的用户名')
address=Column(String(64), nullable=False,comment='地址')
if __name__=="__main__":
with server.app.app_context():
db.create_all()
\ No newline at end of file
adb==1.3.0
aliyun-python-sdk-core==2.14.0
aliyun-python-sdk-kms==2.16.2
APScheduler==3.10.4
async-timeout==4.0.3
Authlib==1.3.0
bcrypt==4.1.2
beautifulsoup4==4.12.3
blinker==1.7.0
cachetools==5.3.2
certifi==2023.11.17
cffi==1.16.0
charset-normalizer==3.3.2
click==8.1.7
command-not-found==0.3
crcmod==1.7
cryptography==3.4.8
cssselect==1.2.0
cssutils==2.9.0
dbus-python==1.2.18
distro==1.7.0
distro-info==1.1+ubuntu0.1
docker==7.0.0
docx-mailmerge==0.5.0
docx2pdf==0.1.8
Flask==3.0.3
Flask-SQLAlchemy==3.1.1
greenlet==3.0.3
httplib2==0.20.2
idna==3.6
importlib-metadata==4.6.4
itsdangerous==2.1.2
jeepney==0.7.1
Jinja2==3.1.3
jmespath==0.10.0
jwcrypto==1.5.6
jwt==1.3.1
keyring==23.5.0
launchpadlib==1.10.16
lazr.restfulclient==0.14.4
lazr.uri==1.0.6
libusb1==3.1.0
lxml==5.1.0
MarkupSafe==2.1.3
more-itertools==8.10.0
netifaces==0.11.0
numpy==1.26.4
oauthlib==3.2.0
oss2==2.18.4
packaging==24.0
paramiko==3.4.0
premailer==3.10.0
pycparser==2.21
pycryptodome==3.20.0
PyGObject==3.42.1
PyJWT==2.3.0
PyMySQL==1.1.0
PyNaCl==1.5.0
pyparsing==2.4.7
pypinyin==0.50.0
python-apt==2.4.0+ubuntu2
pytz==2024.1
PyYAML==5.4.1
redis==5.0.3
requests==2.31.0
SecretStorage==3.3.1
simplejson==3.19.2
six==1.16.0
soupsieve==2.5
SQLAlchemy==2.0.29
systemd-python==234
tqdm==4.66.1
typing_extensions==4.10.0
tzlocal==5.2
ubuntu-advantage-tools==8001
ufw==0.36.1
unattended-upgrades==0.1
urllib3==2.1.0
wadllib==1.3.6
websockify==0.11.0
Werkzeug==3.0.2
yagmail==0.15.293
zipp==1.0.0
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment