├── .gitignore ├── Dockerfile ├── Dockerfile_rpc_server ├── TreeNode.py ├── config.py ├── doc_img.png ├── docker-compose.yml ├── main.py ├── main_rpc_server.py ├── readme.md ├── reqiurements.txt ├── rpc_server ├── __init__.py ├── reqiurements.txt ├── rpc_client.py ├── rpc_server.py └── scheduler_script │ ├── __init__.py │ ├── example.py │ └── example_class.py ├── static ├── js │ └── jquery-3.4.1.min.js └── layui │ ├── css │ ├── layui.css │ ├── layui.mobile.css │ └── modules │ │ ├── code.css │ │ ├── laydate │ │ └── default │ │ │ └── laydate.css │ │ └── layer │ │ └── default │ │ ├── icon-ext.png │ │ ├── icon.png │ │ ├── layer.css │ │ ├── loading-0.gif │ │ ├── loading-1.gif │ │ └── loading-2.gif │ ├── font │ ├── iconfont.eot │ ├── iconfont.svg │ ├── iconfont.ttf │ ├── iconfont.woff │ └── iconfont.woff2 │ ├── images │ └── face │ │ ├── 0.gif │ │ ├── 1.gif │ │ ├── 10.gif │ │ ├── 11.gif │ │ ├── 12.gif │ │ ├── 13.gif │ │ ├── 14.gif │ │ ├── 15.gif │ │ ├── 16.gif │ │ ├── 17.gif │ │ ├── 18.gif │ │ ├── 19.gif │ │ ├── 2.gif │ │ ├── 20.gif │ │ ├── 21.gif │ │ ├── 22.gif │ │ ├── 23.gif │ │ ├── 24.gif │ │ ├── 25.gif │ │ ├── 26.gif │ │ ├── 27.gif │ │ ├── 28.gif │ │ ├── 29.gif │ │ ├── 3.gif │ │ ├── 30.gif │ │ ├── 31.gif │ │ ├── 32.gif │ │ ├── 33.gif │ │ ├── 34.gif │ │ ├── 35.gif │ │ ├── 36.gif │ │ ├── 37.gif │ │ ├── 38.gif │ │ ├── 39.gif │ │ ├── 4.gif │ │ ├── 40.gif │ │ ├── 41.gif │ │ ├── 42.gif │ │ ├── 43.gif │ │ ├── 44.gif │ │ ├── 45.gif │ │ ├── 46.gif │ │ ├── 47.gif │ │ ├── 48.gif │ │ ├── 49.gif │ │ ├── 5.gif │ │ ├── 50.gif │ │ ├── 51.gif │ │ ├── 52.gif │ │ ├── 53.gif │ │ ├── 54.gif │ │ ├── 55.gif │ │ ├── 56.gif │ │ ├── 57.gif │ │ ├── 58.gif │ │ ├── 59.gif │ │ ├── 6.gif │ │ ├── 60.gif │ │ ├── 61.gif │ │ ├── 62.gif │ │ ├── 63.gif │ │ ├── 64.gif │ │ ├── 65.gif │ │ ├── 66.gif │ │ ├── 67.gif │ │ ├── 68.gif │ │ ├── 69.gif │ │ ├── 7.gif │ │ ├── 70.gif │ │ ├── 71.gif │ │ ├── 8.gif │ │ └── 9.gif │ ├── lay │ └── modules │ │ ├── carousel.js │ │ ├── code.js │ │ ├── colorpicker.js │ │ ├── element.js │ │ ├── flow.js │ │ ├── form.js │ │ ├── jquery.js │ │ ├── laydate.js │ │ ├── layedit.js │ │ ├── layer.js │ │ ├── laypage.js │ │ ├── laytpl.js │ │ ├── mobile.js │ │ ├── rate.js │ │ ├── slider.js │ │ ├── table.js │ │ ├── transfer.js │ │ ├── tree.js │ │ ├── upload.js │ │ └── util.js │ ├── layui.all.js │ └── layui.js └── templates └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | # general things to ignore 2 | build/ 3 | dist/ 4 | *.egg-info/ 5 | *.egg 6 | *.py[cod] 7 | __pycache__/ 8 | *.so 9 | *~ 10 | 11 | /.idea/ 12 | *.log -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM tiangolo/uvicorn-gunicorn-fastapi:python3.6 2 | ENV TZ=Asia/Shanghai 3 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 4 | 5 | COPY . /app 6 | RUN pip install -i https://mirrors.aliyun.com/pypi/simple/ -r /app/reqiurements.txt 7 | 8 | -------------------------------------------------------------------------------- /Dockerfile_rpc_server: -------------------------------------------------------------------------------- 1 | FROM python:3.6-slim-buster 2 | ENV TZ=Asia/Shanghai 3 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 4 | 5 | WORKDIR /usr/src/app 6 | 7 | COPY ./rpc_server ./ 8 | RUN pip install -i https://mirrors.aliyun.com/pypi/simple/ --no-cache-dir -r reqiurements.txt 9 | 10 | EXPOSE 12345 11 | CMD [ "python", "./rpc_server.py" ] -------------------------------------------------------------------------------- /TreeNode.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/30 14:23 3 | # @File : TreeNode.py 4 | # @author : dfkai 5 | # @Software: PyCharm 6 | import json 7 | import os 8 | 9 | 10 | class Node: 11 | def __init__(self, title, children=[]): 12 | self.title = title 13 | self.children = children 14 | 15 | def __str__(self): 16 | return self.title 17 | 18 | def get_name(self): 19 | if self.children: 20 | data = dict(title=self.title, children=[i for i in self.children]) 21 | else: 22 | data = dict(title=self.title) 23 | return data 24 | 25 | 26 | class TreeNode(object): 27 | def __init__(self, script_name: str = ""): 28 | self.script_name = script_name 29 | 30 | def get_node(self, file_path: str, node: Node = None) -> Node: 31 | if not node: 32 | node = Node(self.script_name, []) 33 | for i in os.listdir(file_path): 34 | if os.path.isdir(os.path.join(file_path, i)): 35 | if not i.startswith('__'): 36 | dir_node = self.get_node(os.path.join(file_path, i), Node(i, [])) 37 | node.children.append(dir_node.get_name()) 38 | else: 39 | if i.endswith('.py') and not i.startswith('__'): 40 | dir_node = Node(i, []) 41 | node.children.append(dir_node.get_name()) 42 | return node 43 | 44 | 45 | if __name__ == '__main__': 46 | tree_node = TreeNode('脚本文件') 47 | node = tree_node.get_node(r'C:\Users\libai\dfk-common\日常\scheduler_item\app\schedudler_script') 48 | 49 | print(json.dumps(node.get_name(), indent=4)) 50 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @Date : 2022/1/14 4 | @Author : libaibuaidufu 5 | @Email : libaibuaidufu@gmail.com 6 | """ 7 | DATABASE_URL = 'mysql+pymysql://root:password@localhost:3306/dbname' 8 | -------------------------------------------------------------------------------- /doc_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/doc_img.png -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.0' 2 | services: 3 | rpc_server: 4 | container_name: rpc-server 5 | build: 6 | context: . 7 | dockerfile: Dockerfile_rpc_server 8 | ports: 9 | - "12345:12345" 10 | fastapi_sche: 11 | build: 12 | context: . 13 | dockerfile: Dockerfile 14 | container_name: fastapi-sche 15 | depends_on: 16 | - rpc_server 17 | ports: 18 | - "8080:80" 19 | volumes: 20 | - .:/app 21 | links: 22 | - rpc_server 23 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/25 11:24 3 | # @File : main_rpc_server.py 4 | # @author : dfkai 5 | # @Software: PyCharm 6 | import time 7 | from datetime import datetime 8 | from typing import List 9 | 10 | import rpyc 11 | import sqlalchemy 12 | import uvicorn as u 13 | from databases import Database, DatabaseURL 14 | from fastapi import FastAPI, UploadFile, File 15 | from fastapi.exceptions import HTTPException 16 | from pydantic import BaseModel 17 | from starlette.requests import Request 18 | from starlette.staticfiles import StaticFiles 19 | from starlette.templating import Jinja2Templates 20 | from config import DATABASE_URL 21 | from rpc_server.rpc_server import logger 22 | 23 | app = FastAPI() 24 | dburl = DatabaseURL(DATABASE_URL) 25 | database = Database(dburl) 26 | 27 | app.mount("/static", StaticFiles(directory="static"), name="static") 28 | 29 | # 创建一个templates(模板)对象,以后可以重用。 30 | templates = Jinja2Templates(directory="templates") 31 | 32 | # 下面是使用sqlalchemy连接 33 | metadata = sqlalchemy.MetaData() 34 | 35 | sche = sqlalchemy.Table( 36 | "scheduler", 37 | metadata, 38 | sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True), 39 | sqlalchemy.Column('script_name', sqlalchemy.String(100), nullable=False, comment='脚本位置'), 40 | sqlalchemy.Column('schedule_name', sqlalchemy.String(50), comment='定时任务名称'), 41 | sqlalchemy.Column('schedule_desc', sqlalchemy.String(255), comment='定时任务描述'), 42 | sqlalchemy.Column('cron_second', sqlalchemy.String(20), default='*', comment='定时任务-秒'), 43 | sqlalchemy.Column('cron_minutes', sqlalchemy.String(20), default='*', comment='定时任务-分'), 44 | sqlalchemy.Column('cron_hour', sqlalchemy.String(20), default='*', comment='定时任务-时'), 45 | sqlalchemy.Column('cron_day_of_month', sqlalchemy.String(50), default='*', comment='定时任务-每月几号'), 46 | sqlalchemy.Column('cron_day_of_week', sqlalchemy.String(50), default='*', comment='定时任务-每周星期几'), 47 | sqlalchemy.Column('cron_month', sqlalchemy.String(20), default='*', comment='定时任务-月'), 48 | sqlalchemy.Column('run_type', sqlalchemy.String(1), comment='运行类型 1 启动'), 49 | sqlalchemy.Column('enabled', sqlalchemy.SmallInteger), 50 | sqlalchemy.Column('is_lock', sqlalchemy.SmallInteger), 51 | sqlalchemy.Column('priority', sqlalchemy.SmallInteger), 52 | sqlalchemy.Column('create_time', sqlalchemy.DateTime, default=datetime.now), 53 | ) 54 | 55 | engine = sqlalchemy.create_engine( 56 | DATABASE_URL 57 | ) 58 | metadata.create_all(engine) 59 | conn = None 60 | bgsrv = None 61 | scheduler = None 62 | 63 | 64 | class JobId(BaseModel): 65 | id: str = None 66 | 67 | 68 | class UpdateJob(BaseModel): 69 | id: str = None 70 | script_name: str = None 71 | schedule_name: str = None 72 | schedule_desc: str = None 73 | cron_second: str = None 74 | cron_minutes: str = None 75 | cron_hour: str = None 76 | cron_day_of_month: str = None 77 | cron_day_of_week: str = None 78 | cron_month: str = None 79 | 80 | 81 | class AddJob(BaseModel): 82 | script_name: str = None 83 | schedule_name: str = None 84 | schedule_desc: str = None 85 | cron_second: str = None 86 | cron_minutes: str = None 87 | cron_hour: str = None 88 | cron_day_of_month: str = None 89 | cron_day_of_week: str = None 90 | cron_month: str = None 91 | 92 | 93 | @app.on_event('startup') 94 | async def startup(): 95 | global conn, bgsrv, scheduler 96 | 97 | try: 98 | conn = rpyc.connect("rpc_server", 12345) 99 | # create a bg thread to process incoming events 100 | bgsrv = rpyc.BgServingThread(conn) 101 | scheduler = conn.root 102 | except ConnectionRefusedError as e: 103 | logger.error("请先执行 rpc_server.py,否则无法使用web") 104 | raise 105 | await database.connect() 106 | 107 | 108 | @app.on_event("shutdown") 109 | async def shutdown(): 110 | conn.close() 111 | await database.disconnect() 112 | 113 | 114 | @app.get("/") 115 | async def index(request: Request): 116 | # 获取数据库中job 117 | query = sche.select().reduce_columns([sche.c.id]) 118 | job_db_results = await database.fetch_all(query) 119 | job_results: List[dict] = [] 120 | 121 | for table_i in job_db_results: 122 | table_i = dict(table_i) 123 | table_i['create_time'] = str(table_i['create_time']) 124 | table_i['task_plan'] = ' '.join(list(table_i.values())[4:10]) 125 | job_results.append(table_i) 126 | # 获取运行中的job 127 | jobs = scheduler.get_jobs() 128 | run_jobs = [] 129 | for i in jobs: 130 | cron_info_dict = dict() 131 | next_run_time = "" 132 | if hasattr(i, 'next_run_time'): 133 | next_run_time = str(i.next_run_time.strftime("%Y-%m-%d %H:%M:%S")) if i.next_run_time else "" 134 | if hasattr(i.trigger, 'fields'): 135 | for value in i.trigger.fields: 136 | cron_info_dict[str(value.name)] = str(value) 137 | # print(value.name,value) 138 | cron_info_val = list(cron_info_dict.values()) 139 | cron_info_val.reverse() 140 | cron_info = " ".join(cron_info_val) 141 | run_jobs.append({ 142 | 'id': i.id, 143 | 'name': i.name, 144 | 'pending': i.pending + 0, 145 | 'cron_info_dict': cron_info_dict, 146 | 'cron_info': cron_info, 147 | 'next_run_time': next_run_time, 148 | 'func_ref': i.func_ref, 149 | }) 150 | 151 | # 获取脚本文件 152 | script_name_list = [] 153 | return templates.TemplateResponse("index.html", 154 | {"request": request, 'run_jobs': run_jobs, 'job_results': job_results, 155 | 'script_name_list': script_name_list}) 156 | 157 | 158 | @app.get("/print_jobs/") 159 | async def print_jobs(): 160 | scheduler.print_jobs() 161 | return {"reslut": "succ"} 162 | 163 | 164 | @app.get("/close_aps/") 165 | async def close_aps(): 166 | scheduler.shutdown() 167 | 168 | 169 | @app.get('/start_aps/') 170 | async def start_aps(): 171 | scheduler.start(paused=True) 172 | 173 | 174 | @app.post("/add_job/") 175 | async def add_job(job: JobId): 176 | # 添加调度任务 177 | query = sche.select().where(sche.c.id == job.id) 178 | result = await database.fetch_one(query) 179 | result_dict = dict(result) 180 | job_id = f'{job.id}_{int(time.time())}' 181 | try: 182 | scheduler.add_job(result_dict.get('script_name'), 183 | trigger='cron', 184 | id=job_id, 185 | name=result_dict.get('schedule_name'), 186 | misfire_grace_time=60 * 60, 187 | coalesce=True, 188 | max_instances=999, 189 | second=result_dict.get("cron_second"), 190 | minute=result_dict.get("cron_minutes"), 191 | hour=result_dict.get("cron_hour"), 192 | day=result_dict.get("cron_day_of_month"), 193 | day_of_week=result_dict.get("cron_day_of_week"), 194 | month=result_dict.get("cron_month"), 195 | ) 196 | return {'result': 'succ'} 197 | except Exception as e: 198 | logger.error(e) 199 | raise HTTPException(status_code=404, detail="id not found") 200 | 201 | 202 | @app.post("/reschedule_job/") 203 | async def reschedule_job(job: UpdateJob): 204 | try: 205 | scheduler.reschedule_job( 206 | job_id=job.id, 207 | trigger='cron', 208 | second=job.cron_second, 209 | minute=job.cron_minutes, 210 | hour=job.cron_hour, 211 | day=job.cron_day_of_month, 212 | day_of_week=job.cron_day_of_week, 213 | month=job.cron_month 214 | ) 215 | return {'result': 'succ'} 216 | except Exception as e: 217 | logger.error(e) 218 | raise HTTPException(status_code=404, detail="id not found") 219 | 220 | 221 | @app.post("/remove_job/") 222 | async def remove_job(job: JobId): 223 | try: 224 | scheduler.remove_job(job_id=job.id) 225 | return {'result': 'succ'} 226 | except Exception as e: 227 | logger.error(e) 228 | raise HTTPException(status_code=404, detail="id not found") 229 | 230 | 231 | @app.post('/modify_job/') 232 | async def modify_job(job: JobId): 233 | try: 234 | scheduler.modify_job(job_id=job.id) 235 | return {'result': 'succ'} 236 | except Exception as e: 237 | logger.error(e) 238 | raise HTTPException(status_code=404, detail="id not found") 239 | 240 | 241 | @app.post("/pause_job/") 242 | async def pause_job(job: JobId): 243 | try: 244 | scheduler.pause_job(job.id) 245 | return {'result': 'succ'} 246 | except Exception as e: 247 | logger.error(e) 248 | raise HTTPException(status_code=404, detail="id not found") 249 | 250 | 251 | @app.post("/resume_job/") 252 | async def resume_job(job: JobId): 253 | try: 254 | scheduler.resume_job(job.id) 255 | return {'result': 'succ'} 256 | except Exception as e: 257 | logger.error(e) 258 | raise HTTPException(status_code=404, detail="id not found") 259 | 260 | 261 | @app.post("/update_db_job/") 262 | async def update_db_job(job: UpdateJob): 263 | update_sql = f"update scheduler set schedule_name='{job.schedule_name}' ,schedule_desc='{job.schedule_desc}',cron_second='{job.cron_second}',cron_minutes='{job.cron_minutes}',cron_hour='{job.cron_hour}',cron_day_of_month='{job.cron_day_of_month}',cron_day_of_week='{job.cron_day_of_week}',cron_month='{job.cron_month}',script_name='{job.script_name}' where id={job.id}" 264 | await database.execute(update_sql) 265 | 266 | return {'result': 'succ'} 267 | 268 | 269 | @app.post('/add_db_job/') 270 | async def add_db_job(job: AddJob): 271 | now = datetime.now() 272 | query = sche.insert() 273 | values = dict( 274 | script_name=job.script_name, 275 | schedule_name=job.schedule_name, 276 | schedule_desc=job.schedule_desc, 277 | cron_second=job.cron_second, 278 | cron_minutes=job.cron_minutes, 279 | cron_hour=job.cron_hour, 280 | cron_day_of_month=job.cron_day_of_month, 281 | cron_day_of_week=job.cron_day_of_week, 282 | cron_month=job.cron_month, 283 | run_type=1, 284 | enabled=0, 285 | is_lock=1, 286 | priority=999, 287 | create_time=now, 288 | ) 289 | await database.execute(query=query, values=values) 290 | 291 | return {'result': 'succ'} 292 | 293 | 294 | @app.post('/delete_db_job/') 295 | async def delete_db_job(job: UpdateJob): 296 | update_sql = f"delete from scheduler where id={job.id}" 297 | await database.execute(update_sql) 298 | return {'result': 'succ'} 299 | 300 | 301 | @app.post("/upload/") 302 | async def upload_file(file: UploadFile = File(...)): 303 | try: 304 | return {"filename": file.filename, 'result': 'fail', 'msg': "请使用 main_rpc_server.py 才可以使用上传!"} 305 | except Exception as e: 306 | raise HTTPException(status_code=400, detail="file upload fail") 307 | 308 | 309 | if __name__ == '__main__': 310 | u.run(app, host="0.0.0.0", port=8081) 311 | -------------------------------------------------------------------------------- /main_rpc_server.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/25 11:24 3 | # @File : main_rpc_server.py 4 | # @author : dfkai 5 | # @Software: PyCharm 6 | import os 7 | import time 8 | from datetime import datetime 9 | from typing import List 10 | 11 | import rpyc 12 | import sqlalchemy 13 | import uvicorn as u 14 | from databases import Database, DatabaseURL 15 | from fastapi import FastAPI, UploadFile, File 16 | from fastapi.exceptions import HTTPException 17 | from pydantic import BaseModel 18 | from starlette.requests import Request 19 | from starlette.staticfiles import StaticFiles 20 | from starlette.templating import Jinja2Templates 21 | 22 | from TreeNode import TreeNode 23 | from config import DATABASE_URL 24 | from rpc_server.rpc_server import logger 25 | 26 | scheduler_script_name = 'rpc_server/scheduler_script' 27 | 28 | script_path = os.path.join(os.getcwd(), scheduler_script_name) 29 | if not os.path.exists(script_path): 30 | os.mkdir(script_path) 31 | 32 | app = FastAPI() 33 | dburl = DatabaseURL(DATABASE_URL) 34 | database = Database(dburl) 35 | 36 | app.mount("/static", StaticFiles(directory="static"), name="static") 37 | 38 | # 创建一个templates(模板)对象,以后可以重用。 39 | templates = Jinja2Templates(directory="templates") 40 | 41 | # 下面是使用sqlalchemy连接 42 | metadata = sqlalchemy.MetaData() 43 | 44 | sche = sqlalchemy.Table( 45 | "scheduler", 46 | metadata, 47 | sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True), # int 48 | sqlalchemy.Column('script_name', sqlalchemy.String(100), nullable=False, comment='脚本位置'), # varchar 49 | sqlalchemy.Column('schedule_name', sqlalchemy.String(50), comment='定时任务名称'), # varchar 50 | sqlalchemy.Column('schedule_desc', sqlalchemy.String(255), comment='定时任务描述'), # varchar 51 | sqlalchemy.Column('cron_second', sqlalchemy.String(20), default='*', comment='定时任务-秒'), # varchar 52 | sqlalchemy.Column('cron_minutes', sqlalchemy.String(20), default='*', comment='定时任务-分'), # varchar 53 | sqlalchemy.Column('cron_hour', sqlalchemy.String(20), default='*', comment='定时任务-时'), # varchar 54 | sqlalchemy.Column('cron_day_of_month', sqlalchemy.String(50), default='*', comment='定时任务-每月几号'), # varchar 55 | sqlalchemy.Column('cron_day_of_week', sqlalchemy.String(50), default='*', comment='定时任务-每周星期几'), # varchar 56 | sqlalchemy.Column('cron_month', sqlalchemy.String(20), default='*', comment='定时任务-月'), # varchar 57 | sqlalchemy.Column('run_type', sqlalchemy.String(1), comment='运行类型 1 启动'), # varchar 58 | sqlalchemy.Column('enabled', sqlalchemy.SmallInteger, comment=''), # int 59 | sqlalchemy.Column('is_lock', sqlalchemy.SmallInteger, comment=''), # int 60 | sqlalchemy.Column('priority', sqlalchemy.SmallInteger, comment=''), # int 61 | sqlalchemy.Column('create_time', sqlalchemy.DateTime, default=datetime.now), # datetime 62 | ) 63 | 64 | engine = sqlalchemy.create_engine( 65 | DATABASE_URL 66 | ) 67 | metadata.create_all(engine) 68 | conn = None 69 | bgsrv = None 70 | scheduler = None 71 | 72 | 73 | class JobId(BaseModel): 74 | id: str = None 75 | 76 | 77 | class UpdateJob(BaseModel): 78 | id: str = None 79 | script_name: str = None 80 | schedule_name: str = None 81 | schedule_desc: str = None 82 | cron_second: str = None 83 | cron_minutes: str = None 84 | cron_hour: str = None 85 | cron_day_of_month: str = None 86 | cron_day_of_week: str = None 87 | cron_month: str = None 88 | 89 | 90 | class AddJob(BaseModel): 91 | script_name: str = None 92 | schedule_name: str = None 93 | schedule_desc: str = None 94 | cron_second: str = None 95 | cron_minutes: str = None 96 | cron_hour: str = None 97 | cron_day_of_month: str = None 98 | cron_day_of_week: str = None 99 | cron_month: str = None 100 | 101 | 102 | @app.on_event('startup') 103 | async def startup(): 104 | global conn, bgsrv, scheduler 105 | 106 | try: 107 | conn = rpyc.connect("localhost", 12345) 108 | # create a bg thread to process incoming events 109 | bgsrv = rpyc.BgServingThread(conn) 110 | scheduler = conn.root 111 | except ConnectionRefusedError as e: 112 | logger.error("请先执行 rpc_server.py,否则无法使用web") 113 | raise 114 | await database.connect() 115 | 116 | 117 | @app.on_event("shutdown") 118 | async def shutdown(): 119 | conn.close() 120 | await database.disconnect() 121 | 122 | 123 | @app.get("/") 124 | async def index(request: Request): 125 | # 获取数据库中job 126 | query = sche.select().reduce_columns([sche.c.id]) 127 | job_db_results = await database.fetch_all(query) 128 | job_results: List[dict] = [] 129 | 130 | for table_i in job_db_results: 131 | table_i = dict(table_i) 132 | table_i['create_time'] = str(table_i['create_time']) 133 | table_i['task_plan'] = ' '.join(list(table_i.values())[4:10]) 134 | job_results.append(table_i) 135 | # 获取运行中的job 136 | jobs = scheduler.get_jobs() 137 | run_jobs = [] 138 | for i in jobs: 139 | cron_info_dict = dict() 140 | next_run_time = "" 141 | if hasattr(i, 'next_run_time'): 142 | next_run_time = str(i.next_run_time.strftime("%Y-%m-%d %H:%M:%S")) if i.next_run_time else "" 143 | if hasattr(i.trigger, 'fields'): 144 | for value in i.trigger.fields: 145 | cron_info_dict[str(value.name)] = str(value) 146 | # print(value.name,value) 147 | cron_info_val = list(cron_info_dict.values()) 148 | cron_info_val.reverse() 149 | cron_info = " ".join(cron_info_val) 150 | run_jobs.append({ 151 | 'id': i.id, 152 | 'name': i.name, 153 | 'pending': i.pending + 0, 154 | 'cron_info_dict': cron_info_dict, 155 | 'cron_info': cron_info, 156 | 'next_run_time': next_run_time, 157 | 'func_ref': i.func_ref, 158 | }) 159 | 160 | # 获取脚本文件 161 | tree_node = TreeNode('脚本文件') 162 | script_name_list = [tree_node.get_node(script_path).get_name()] 163 | return templates.TemplateResponse("index.html", 164 | {"request": request, 'run_jobs': run_jobs, 'job_results': job_results, 165 | 'script_name_list': script_name_list}) 166 | 167 | 168 | @app.get("/print_jobs/") 169 | async def print_jobs(): 170 | scheduler.print_jobs() 171 | return {"reslut": "succ"} 172 | 173 | 174 | @app.get("/close_aps/") 175 | async def close_aps(): 176 | scheduler.shutdown() 177 | 178 | 179 | @app.get('/start_aps/') 180 | async def start_aps(): 181 | scheduler.start(paused=True) 182 | 183 | 184 | @app.post("/add_job/") 185 | async def add_job(job: JobId): 186 | # 添加调度任务 187 | query = sche.select().where(sche.c.id == job.id) 188 | result = await database.fetch_one(query) 189 | result_dict = dict(result) 190 | job_id = f'{job.id}_{int(time.time())}' 191 | try: 192 | scheduler.add_job(result_dict.get('script_name'), 193 | trigger='cron', 194 | id=job_id, 195 | name=result_dict.get('schedule_name'), 196 | misfire_grace_time=60 * 60, 197 | coalesce=True, 198 | max_instances=999, 199 | second=result_dict.get("cron_second"), 200 | minute=result_dict.get("cron_minutes"), 201 | hour=result_dict.get("cron_hour"), 202 | day=result_dict.get("cron_day_of_month"), 203 | day_of_week=result_dict.get("cron_day_of_week"), 204 | month=result_dict.get("cron_month"), 205 | ) 206 | return {'result': 'succ'} 207 | except Exception as e: 208 | logger.error(e) 209 | raise HTTPException(status_code=404, detail="id not found") 210 | 211 | 212 | @app.post("/reschedule_job/") 213 | async def reschedule_job(job: UpdateJob): 214 | try: 215 | scheduler.reschedule_job( 216 | job_id=job.id, 217 | trigger='cron', 218 | second=job.cron_second, 219 | minute=job.cron_minutes, 220 | hour=job.cron_hour, 221 | day=job.cron_day_of_month, 222 | day_of_week=job.cron_day_of_week, 223 | month=job.cron_month 224 | ) 225 | return {'result': 'succ'} 226 | except Exception as e: 227 | logger.error(e) 228 | raise HTTPException(status_code=404, detail="id not found") 229 | 230 | 231 | @app.post("/remove_job/") 232 | async def remove_job(job: JobId): 233 | try: 234 | scheduler.remove_job(job_id=job.id) 235 | return {'result': 'succ'} 236 | except Exception as e: 237 | logger.error(e) 238 | raise HTTPException(status_code=404, detail="id not found") 239 | 240 | 241 | @app.post('/modify_job/') 242 | async def modify_job(job: JobId): 243 | try: 244 | scheduler.modify_job(job_id=job.id) 245 | return {'result': 'succ'} 246 | except Exception as e: 247 | logger.error(e) 248 | raise HTTPException(status_code=404, detail="id not found") 249 | 250 | 251 | @app.post("/pause_job/") 252 | async def pause_job(job: JobId): 253 | try: 254 | scheduler.pause_job(job.id) 255 | return {'result': 'succ'} 256 | except Exception as e: 257 | logger.error(e) 258 | raise HTTPException(status_code=404, detail="id not found") 259 | 260 | 261 | @app.post("/resume_job/") 262 | async def resume_job(job: JobId): 263 | try: 264 | scheduler.resume_job(job.id) 265 | return {'result': 'succ'} 266 | except Exception as e: 267 | logger.error(e) 268 | raise HTTPException(status_code=404, detail="id not found") 269 | 270 | 271 | @app.post("/update_db_job/") 272 | async def update_db_job(job: UpdateJob): 273 | update_sql = f"update scheduler set schedule_name='{job.schedule_name}' ,schedule_desc='{job.schedule_desc}',cron_second='{job.cron_second}',cron_minutes='{job.cron_minutes}',cron_hour='{job.cron_hour}',cron_day_of_month='{job.cron_day_of_month}',cron_day_of_week='{job.cron_day_of_week}',cron_month='{job.cron_month}',script_name='{job.script_name}' where id={job.id}" 274 | await database.execute(update_sql) 275 | 276 | return {'result': 'succ'} 277 | 278 | 279 | @app.post('/add_db_job/') 280 | async def add_db_job(job: AddJob): 281 | now = datetime.now() 282 | query = sche.insert() 283 | values = dict( 284 | script_name=job.script_name, 285 | schedule_name=job.schedule_name, 286 | schedule_desc=job.schedule_desc, 287 | cron_second=job.cron_second, 288 | cron_minutes=job.cron_minutes, 289 | cron_hour=job.cron_hour, 290 | cron_day_of_month=job.cron_day_of_month, 291 | cron_day_of_week=job.cron_day_of_week, 292 | cron_month=job.cron_month, 293 | run_type=1, 294 | enabled=0, 295 | is_lock=1, 296 | priority=999, 297 | create_time=now, 298 | ) 299 | await database.execute(query=query, values=values) 300 | 301 | return {'result': 'succ'} 302 | 303 | 304 | @app.post('/delete_db_job/') 305 | async def delete_db_job(job: UpdateJob): 306 | update_sql = f"delete from scheduler where id={job.id}" 307 | await database.execute(update_sql) 308 | return {'result': 'succ'} 309 | 310 | 311 | @app.post("/upload/") 312 | async def upload_file(file: UploadFile = File(...)): 313 | try: 314 | file_path = os.path.join(script_path, file.filename) 315 | if os.path.exists(file_path): 316 | return {'result': 'fail', 'msg': '文件名重复'} 317 | with open(file_path, 'wb') as f: 318 | f.write(file.file.read()) 319 | return {"filename": file.filename, 'result': 'succ'} 320 | except Exception as e: 321 | raise HTTPException(status_code=400, detail="file upload fail") 322 | 323 | 324 | @app.get('/list_file/') 325 | async def list_file(): 326 | def has_py(file: str) -> bool: 327 | if file.endswith('.py') and not file.startswith('__'): 328 | return True 329 | return False 330 | 331 | data = list(filter(has_py, os.listdir(script_path))) 332 | script_path_list = [] 333 | for index, i in enumerate(data): 334 | script_path_list.append( 335 | {"value": index + 1, "title": i, "disabled": "", "checked": ""} 336 | 337 | ) 338 | return {'data': script_path_list} 339 | 340 | 341 | if __name__ == '__main__': 342 | u.run(app, host="0.0.0.0", port=8081) 343 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Fastapi-scheduler 2 | 3 | 这是一个 fastapi 结合 apscheduler 做的一个动态添加定时任务的web 4 | 5 | ## 快速使用: 6 | 7 | **注意: 修改 main.py 或者 main_rpc_server.py 的 mysql连接** 8 | 9 | **创建数据库** 10 | 修改```DATABASE_URL = f'mysql+pymysql://root:password@localhost:3306/dbname'```中的配置内容 11 | 12 | **获取项目** 13 | 14 | ```bash 15 | git clone https://github.com/libaibuaidufu/Fastapi-scheduler.git 16 | ``` 17 | 18 | **1.本地启动:** 19 | 20 | 1. 启动 rpc-server.py 21 | 22 | ```bash 23 | cd rpc_server 24 | python rpc-server.py 25 | ``` 26 | 27 | 2. 启动 main_rpc_server.py 28 | 29 | ``` 30 | python main_rpc_server.py 31 | ``` 32 | 33 | 3. 打开 http://localhost:8000 34 | 35 | **2.docker部署:** 36 | 37 | 1. 使用 docker-compose 38 | 39 | ``` 40 | docker-compose up -d 41 | ``` 42 | 43 | 2. 打开:http://ip:8080 44 | 45 | **3.删除项目:** 46 | 47 | 1. 删除 `docker-compose down` 48 | 49 | **4.复制脚本、安装指定库及添加任务:** 50 | 51 | 1. 复制脚本到 rpc_server 容器里面 52 | 53 | ```bash 54 | docker cp xx.py rpc-server:/usr/src/app/scheduler_script/ 55 | ``` 56 | 57 | 2. 安装 脚本中使用到的 库 58 | 59 | ```bash 60 | docker exec -it rpc_server pip install xxx 61 | ``` 62 | 63 | 3. 再到 web 中去添加任务 再启动任务后,查询有点慢。。。 64 | 65 | ## 已实现功能: 66 | 67 | - 存储配置到 mysql,并实现增删该查 68 | - 实时修改运行中的任务 69 | - 上传脚本 及查看脚本列表 70 | 71 | ## 后期加入功能: 72 | 73 | - 分页 74 | - 脚本下载,上传文件夹 75 | - 加入 jobstore (原因 本来已经加入了 ,结果在执行我自己的一个脚本时,出现pickle 错误就暂时没有加入,可以自己在 aps_server.py 中配置) 76 | - 配置脚本初始化参数 args,kwargs 77 | 78 | ## 已知问题: 79 | 80 | - 脚本执行后,对脚本修改,再次执行可能无效。(原因可能是 第一次已经对脚本进行加载无法再次更新) 81 | - 执行 class 类时,用函数在外面运行最好 ,类似 example_class.py 82 | - 拷贝 rpc_server 里面的脚本 ,如果执行报错后,就算修改后再次执行也会出错 ,所以只能修改文件名称 再次 拷贝使用。 83 | 84 | ## 预览 85 | 86 |  87 | 88 | -------------------------------------------------------------------------------- /reqiurements.txt: -------------------------------------------------------------------------------- 1 | aiocontextvars==0.2.2 2 | aiofiles==0.4.0 3 | aiohttp==3.6.2 4 | aiomysql==0.0.20 5 | APScheduler==3.6.3 6 | async-timeout==3.0.1 7 | attrs==19.3.0 8 | certifi==2019.11.28 9 | cffi==1.14.0 10 | chardet==3.0.4 11 | click==7.1.1 12 | contextvars==2.4 13 | cryptography==2.8 14 | databases==0.2.6 15 | dataclasses==0.7 16 | et-xmlfile==1.0.1 17 | fastapi==0.52.0 18 | h11==0.9.0 19 | idna==2.9 20 | idna-ssl==1.1.0 21 | immutables==0.11 22 | jdcal==1.4.1 23 | Jinja2==2.11.1 24 | MarkupSafe==1.1.1 25 | multidict==4.7.5 26 | openpyxl==3.0.3 27 | plumbum==1.6.9 28 | pycparser==2.20 29 | pydantic==1.4 30 | pyDes==2.0.1 31 | PyMySQL==0.9.2 32 | python-multipart==0.0.5 33 | pytz==2019.3 34 | requests==2.23.0 35 | rpyc==4.1.4 36 | six==1.14.0 37 | SQLAlchemy==1.3.15 38 | starlette==0.13.2 39 | typing-extensions==3.7.4.1 40 | tzlocal==2.0.0 41 | urllib3==1.25.8 42 | uvicorn==0.11.3 43 | websockets==8.1 44 | yarl==1.4.2 45 | -------------------------------------------------------------------------------- /rpc_server/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/31 9:01 3 | # @File : __init__.py.py 4 | # @author : dfkai 5 | # @Software: PyCharm 6 | -------------------------------------------------------------------------------- /rpc_server/reqiurements.txt: -------------------------------------------------------------------------------- 1 | APScheduler==3.6.3 2 | rpyc==4.1.4 -------------------------------------------------------------------------------- /rpc_server/rpc_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/27 14:39 3 | # @File : rpc_client.py 4 | # @author : dfkai 5 | # @Software: PyCharm 6 | """ 7 | This is an example RPC client that connects to the RPyC based scheduler service. 8 | It first connects to the RPyC server on localhost:12345. 9 | Then it schedules a job to run on 2 second intervals and sleeps for 10 seconds. 10 | After that, it unschedules the job and exits. 11 | """ 12 | 13 | from time import sleep 14 | 15 | import rpyc 16 | 17 | conn = rpyc.connect('localhost', 12345) 18 | job = conn.root.add_job('scheduler_script.test:print_text', 'interval', args=['Hello, World'], seconds=2) 19 | sleep(10) 20 | conn.root.remove_job(job.id) 21 | -------------------------------------------------------------------------------- /rpc_server/rpc_server.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/27 14:39 3 | # @File : rpc_server.py 4 | # @author : dfkai 5 | # @Software: PyCharm 6 | """ 7 | This is an example showing how to make the scheduler into a remotely accessible service. 8 | It uses RPyC to set up a service through which the scheduler can be made to add, modify and remove 9 | jobs. 10 | To run, first install RPyC using pip. Then change the working directory to the ``rpc`` directory 11 | and run it with ``python -m server``. 12 | """ 13 | 14 | import logging 15 | 16 | import rpyc 17 | from apscheduler.executors.pool import ProcessPoolExecutor 18 | from apscheduler.schedulers.background import BackgroundScheduler 19 | from rpyc.utils.server import ThreadedServer 20 | 21 | logger = logging.getLogger('apscheduler') 22 | logger.setLevel(logging.DEBUG) 23 | 24 | logger_handler = logging.FileHandler('scheduler.log', mode='a', encoding='utf-8') 25 | logger_handler.setLevel(logging.INFO) 26 | logger_handler.setFormatter(logging.Formatter(fmt='%(asctime)s %(levelname)s: %(message)s', 27 | datefmt='%Y-%m-%d %H:%M:%S')) 28 | logger.addHandler(logger_handler) 29 | 30 | 31 | class SchedulerService(rpyc.Service): 32 | def exposed_add_job(self, func, *args, **kwargs): 33 | return scheduler.add_job(func, *args, **kwargs) 34 | 35 | def exposed_modify_job(self, job_id, jobstore=None, **changes): 36 | return scheduler.modify_job(job_id, jobstore, **changes) 37 | 38 | def exposed_reschedule_job(self, job_id, jobstore=None, trigger=None, **trigger_args): 39 | return scheduler.reschedule_job(job_id, jobstore, trigger, **trigger_args) 40 | 41 | def exposed_pause_job(self, job_id, jobstore=None): 42 | return scheduler.pause_job(job_id, jobstore) 43 | 44 | def exposed_resume_job(self, job_id, jobstore=None): 45 | return scheduler.resume_job(job_id, jobstore) 46 | 47 | def exposed_remove_job(self, job_id, jobstore=None): 48 | scheduler.remove_job(job_id, jobstore) 49 | 50 | def exposed_get_job(self, job_id): 51 | return scheduler.get_job(job_id) 52 | 53 | def exposed_get_jobs(self, jobstore=None): 54 | return scheduler.get_jobs(jobstore) 55 | 56 | def exposed_print_jobs(self, jobstore=None): 57 | return scheduler.print_jobs(jobstore) 58 | 59 | 60 | if __name__ == '__main__': 61 | """初始化""" 62 | jobstores = { 63 | # 'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite') # SQLAlchemyJobStore指定存储链接 # 由于pickle sqlalchemy 报错 只能取消存储 64 | } 65 | executors = { 66 | 'default': {'type': 'threadpool', 'max_workers': 20}, # 最大工作线程数20 67 | 'processpool': ProcessPoolExecutor(max_workers=5) # 最大工作进程数为5 68 | } 69 | scheduler = BackgroundScheduler() 70 | scheduler.configure(jobstores=jobstores, executors=executors) 71 | scheduler.start() 72 | protocol_config = {'allow_public_attrs': True} 73 | server = ThreadedServer(SchedulerService, port=12345, protocol_config=protocol_config) 74 | try: 75 | print("start ") 76 | server.start() 77 | except (KeyboardInterrupt, SystemExit): 78 | pass 79 | finally: 80 | scheduler.shutdown() 81 | -------------------------------------------------------------------------------- /rpc_server/scheduler_script/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/25 11:42 3 | # @File : __init__.py.py 4 | # @author : dfkai 5 | # @Software: PyCharm 6 | -------------------------------------------------------------------------------- /rpc_server/scheduler_script/example.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/30 15:30 3 | # @File : example.py 4 | # @author : dfkai 5 | # @Software: PyCharm 6 | from datetime import datetime 7 | 8 | 9 | def run(): 10 | print(datetime.now(), 'hello,world!') 11 | -------------------------------------------------------------------------------- /rpc_server/scheduler_script/example_class.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/30 15:33 3 | # @File : example_class.py 4 | # @author : dfkai 5 | # @Software: PyCharm 6 | from datetime import datetime 7 | 8 | 9 | class Hello: 10 | def main(self): 11 | print(datetime.now(), 'hello,world!') 12 | 13 | 14 | def run(): 15 | hello = Hello() 16 | hello.main() 17 | -------------------------------------------------------------------------------- /static/layui/css/layui.mobile.css: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | blockquote,body,button,dd,div,dl,dt,form,h1,h2,h3,h4,h5,h6,input,legend,li,ol,p,td,textarea,th,ul{margin:0;padding:0;-webkit-tap-highlight-color:rgba(0,0,0,0)}html{font:12px 'Helvetica Neue','PingFang SC',STHeitiSC-Light,Helvetica,Arial,sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}a,button,input{-webkit-tap-highlight-color:rgba(255,0,0,0)}a{text-decoration:none;background:0 0}a:active,a:hover{outline:0}table{border-collapse:collapse;border-spacing:0}li{list-style:none}b,strong{font-weight:700}h1,h2,h3,h4,h5,h6{font-weight:500}address,cite,dfn,em,var{font-style:normal}dfn{font-style:italic}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}img{border:0;vertical-align:bottom}.layui-inline,input,label{vertical-align:middle}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0;outline:0}button,select{text-transform:none}select{-webkit-appearance:none;border:none}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}@font-face{font-family:layui-icon;src:url(../font/iconfont.eot?v=1.0.7);src:url(../font/iconfont.eot?v=1.0.7#iefix) format('embedded-opentype'),url(../font/iconfont.woff?v=1.0.7) format('woff'),url(../font/iconfont.ttf?v=1.0.7) format('truetype'),url(../font/iconfont.svg?v=1.0.7#iconfont) format('svg')}.layui-icon{font-family:layui-icon!important;font-size:16px;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.layui-box,.layui-box *{-webkit-box-sizing:content-box!important;-moz-box-sizing:content-box!important;box-sizing:content-box!important}.layui-border-box,.layui-border-box *{-webkit-box-sizing:border-box!important;-moz-box-sizing:border-box!important;box-sizing:border-box!important}.layui-inline{position:relative;display:inline-block;*display:inline;*zoom:1}.layui-edge,.layui-upload-iframe{position:absolute;width:0;height:0}.layui-edge{border-style:dashed;border-color:transparent;overflow:hidden}.layui-elip{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-unselect{-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.layui-disabled,.layui-disabled:active{background-color:#d2d2d2!important;color:#fff!important;cursor:not-allowed!important}.layui-circle{border-radius:100%}.layui-show{display:block!important}.layui-hide{display:none!important}.layui-upload-iframe{border:0;visibility:hidden}.layui-upload-enter{border:1px solid #009E94;background-color:#009E94;color:#fff;-webkit-transform:scale(1.1);transform:scale(1.1)}@-webkit-keyframes layui-m-anim-scale{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes layui-m-anim-scale{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}.layui-m-anim-scale{animation-name:layui-m-anim-scale;-webkit-animation-name:layui-m-anim-scale}@-webkit-keyframes layui-m-anim-up{0%{opacity:0;-webkit-transform:translateY(800px);transform:translateY(800px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes layui-m-anim-up{0%{opacity:0;-webkit-transform:translateY(800px);transform:translateY(800px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}.layui-m-anim-up{-webkit-animation-name:layui-m-anim-up;animation-name:layui-m-anim-up}@-webkit-keyframes layui-m-anim-left{0%{-webkit-transform:translateX(100%);transform:translateX(100%)}100%{-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes layui-m-anim-left{0%{-webkit-transform:translateX(100%);transform:translateX(100%)}100%{-webkit-transform:translateX(0);transform:translateX(0)}}.layui-m-anim-left{-webkit-animation-name:layui-m-anim-left;animation-name:layui-m-anim-left}@-webkit-keyframes layui-m-anim-right{0%{-webkit-transform:translateX(-100%);transform:translateX(-100%)}100%{-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes layui-m-anim-right{0%{-webkit-transform:translateX(-100%);transform:translateX(-100%)}100%{-webkit-transform:translateX(0);transform:translateX(0)}}.layui-m-anim-right{-webkit-animation-name:layui-m-anim-right;animation-name:layui-m-anim-right}@-webkit-keyframes layui-m-anim-lout{0%{-webkit-transform:translateX(0);transform:translateX(0)}100%{-webkit-transform:translateX(-100%);transform:translateX(-100%)}}@keyframes layui-m-anim-lout{0%{-webkit-transform:translateX(0);transform:translateX(0)}100%{-webkit-transform:translateX(-100%);transform:translateX(-100%)}}.layui-m-anim-lout{-webkit-animation-name:layui-m-anim-lout;animation-name:layui-m-anim-lout}@-webkit-keyframes layui-m-anim-rout{0%{-webkit-transform:translateX(0);transform:translateX(0)}100%{-webkit-transform:translateX(100%);transform:translateX(100%)}}@keyframes layui-m-anim-rout{0%{-webkit-transform:translateX(0);transform:translateX(0)}100%{-webkit-transform:translateX(100%);transform:translateX(100%)}}.layui-m-anim-rout{-webkit-animation-name:layui-m-anim-rout;animation-name:layui-m-anim-rout}.layui-m-layer{position:relative;z-index:19891014}.layui-m-layer *{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.layui-m-layermain,.layui-m-layershade{position:fixed;left:0;top:0;width:100%;height:100%}.layui-m-layershade{background-color:rgba(0,0,0,.7);pointer-events:auto}.layui-m-layermain{display:table;font-family:Helvetica,arial,sans-serif;pointer-events:none}.layui-m-layermain .layui-m-layersection{display:table-cell;vertical-align:middle;text-align:center}.layui-m-layerchild{position:relative;display:inline-block;text-align:left;background-color:#fff;font-size:14px;border-radius:5px;box-shadow:0 0 8px rgba(0,0,0,.1);pointer-events:auto;-webkit-overflow-scrolling:touch;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.2s;animation-duration:.2s}.layui-m-layer0 .layui-m-layerchild{width:90%;max-width:640px}.layui-m-layer1 .layui-m-layerchild{border:none;border-radius:0}.layui-m-layer2 .layui-m-layerchild{width:auto;max-width:260px;min-width:40px;border:none;background:0 0;box-shadow:none;color:#fff}.layui-m-layerchild h3{padding:0 10px;height:60px;line-height:60px;font-size:16px;font-weight:400;border-radius:5px 5px 0 0;text-align:center}.layui-m-layerbtn span,.layui-m-layerchild h3{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-m-layercont{padding:50px 30px;line-height:22px;text-align:center}.layui-m-layer1 .layui-m-layercont{padding:0;text-align:left}.layui-m-layer2 .layui-m-layercont{text-align:center;padding:0;line-height:0}.layui-m-layer2 .layui-m-layercont i{width:25px;height:25px;margin-left:8px;display:inline-block;background-color:#fff;border-radius:100%;-webkit-animation:layui-m-anim-loading 1.4s infinite ease-in-out;animation:layui-m-anim-loading 1.4s infinite ease-in-out;-webkit-animation-fill-mode:both;animation-fill-mode:both}.layui-m-layerbtn,.layui-m-layerbtn span{position:relative;text-align:center;border-radius:0 0 5px 5px}.layui-m-layer2 .layui-m-layercont p{margin-top:20px}@-webkit-keyframes layui-m-anim-loading{0%,100%,80%{transform:scale(0);-webkit-transform:scale(0)}40%{transform:scale(1);-webkit-transform:scale(1)}}@keyframes layui-m-anim-loading{0%,100%,80%{transform:scale(0);-webkit-transform:scale(0)}40%{transform:scale(1);-webkit-transform:scale(1)}}.layui-m-layer2 .layui-m-layercont i:first-child{margin-left:0;-webkit-animation-delay:-.32s;animation-delay:-.32s}.layui-m-layer2 .layui-m-layercont i.layui-m-layerload{-webkit-animation-delay:-.16s;animation-delay:-.16s}.layui-m-layer2 .layui-m-layercont>div{line-height:22px;padding-top:7px;margin-bottom:20px;font-size:14px}.layui-m-layerbtn{display:box;display:-moz-box;display:-webkit-box;width:100%;height:50px;line-height:50px;font-size:0;border-top:1px solid #D0D0D0;background-color:#F2F2F2}.layui-m-layerbtn span{display:block;-moz-box-flex:1;box-flex:1;-webkit-box-flex:1;font-size:14px;cursor:pointer}.layui-m-layerbtn span[yes]{color:#40AFFE}.layui-m-layerbtn span[no]{border-right:1px solid #D0D0D0;border-radius:0 0 0 5px}.layui-m-layerbtn span:active{background-color:#F6F6F6}.layui-m-layerend{position:absolute;right:7px;top:10px;width:30px;height:30px;border:0;font-weight:400;background:0 0;cursor:pointer;-webkit-appearance:none;font-size:30px}.layui-m-layerend::after,.layui-m-layerend::before{position:absolute;left:5px;top:15px;content:'';width:18px;height:1px;background-color:#999;transform:rotate(45deg);-webkit-transform:rotate(45deg);border-radius:3px}.layui-m-layerend::after{transform:rotate(-45deg);-webkit-transform:rotate(-45deg)}body .layui-m-layer .layui-m-layer-footer{position:fixed;width:95%;max-width:100%;margin:0 auto;left:0;right:0;bottom:10px;background:0 0}.layui-m-layer-footer .layui-m-layercont{padding:20px;border-radius:5px 5px 0 0;background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn{display:block;height:auto;background:0 0;border-top:none}.layui-m-layer-footer .layui-m-layerbtn span{background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn span[no]{color:#FD482C;border-top:1px solid #c2c2c2;border-radius:0 0 5px 5px}.layui-m-layer-footer .layui-m-layerbtn span[yes]{margin-top:10px;border-radius:5px}body .layui-m-layer .layui-m-layer-msg{width:auto;max-width:90%;margin:0 auto;bottom:-150px;background-color:rgba(0,0,0,.7);color:#fff}.layui-m-layer-msg .layui-m-layercont{padding:10px 20px} -------------------------------------------------------------------------------- /static/layui/css/modules/code.css: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | html #layuicss-skincodecss{display:none;position:absolute;width:1989px}.layui-code-h3,.layui-code-view{position:relative;font-size:12px}.layui-code-view{display:block;margin:10px 0;padding:0;border:1px solid #e2e2e2;border-left-width:6px;background-color:#F2F2F2;color:#333;font-family:Courier New}.layui-code-h3{padding:0 10px;height:32px;line-height:32px;border-bottom:1px solid #e2e2e2}.layui-code-h3 a{position:absolute;right:10px;top:0;color:#999}.layui-code-view .layui-code-ol{position:relative;overflow:auto}.layui-code-view .layui-code-ol li{position:relative;margin-left:45px;line-height:20px;padding:0 5px;border-left:1px solid #e2e2e2;list-style-type:decimal-leading-zero;*list-style-type:decimal;background-color:#fff}.layui-code-view pre{margin:0}.layui-code-notepad{border:1px solid #0C0C0C;border-left-color:#3F3F3F;background-color:#0C0C0C;color:#C2BE9E}.layui-code-notepad .layui-code-h3{border-bottom:none}.layui-code-notepad .layui-code-ol li{background-color:#3F3F3F;border-left:none} -------------------------------------------------------------------------------- /static/layui/css/modules/laydate/default/laydate.css: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | .laydate-set-ym,.layui-laydate,.layui-laydate *,.layui-laydate-list{box-sizing:border-box}html #layuicss-laydate{display:none;position:absolute;width:1989px}.layui-laydate *{margin:0;padding:0}.layui-laydate{position:absolute;z-index:66666666;margin:5px 0;border-radius:2px;font-size:14px;-webkit-animation-duration:.3s;animation-duration:.3s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-name:laydate-upbit;animation-name:laydate-upbit}.layui-laydate-main{width:272px}.layui-laydate-content td,.layui-laydate-header *,.layui-laydate-list li{transition-duration:.3s;-webkit-transition-duration:.3s}@-webkit-keyframes laydate-upbit{from{-webkit-transform:translate3d(0,20px,0);opacity:.3}to{-webkit-transform:translate3d(0,0,0);opacity:1}}@keyframes laydate-upbit{from{transform:translate3d(0,20px,0);opacity:.3}to{transform:translate3d(0,0,0);opacity:1}}.layui-laydate-static{position:relative;z-index:0;display:inline-block;margin:0;-webkit-animation:none;animation:none}.laydate-ym-show .laydate-next-m,.laydate-ym-show .laydate-prev-m{display:none!important}.laydate-ym-show .laydate-next-y,.laydate-ym-show .laydate-prev-y{display:inline-block!important}.laydate-time-show .laydate-set-ym span[lay-type=month],.laydate-time-show .laydate-set-ym span[lay-type=year],.laydate-time-show .layui-laydate-header .layui-icon,.laydate-ym-show .laydate-set-ym span[lay-type=month]{display:none!important}.layui-laydate-header{position:relative;line-height:30px;padding:10px 70px 5px}.laydate-set-ym span,.layui-laydate-header i{padding:0 5px;cursor:pointer}.layui-laydate-header *{display:inline-block;vertical-align:bottom}.layui-laydate-header i{position:absolute;top:10px;color:#999;font-size:18px}.layui-laydate-header i.laydate-prev-y{left:15px}.layui-laydate-header i.laydate-prev-m{left:45px}.layui-laydate-header i.laydate-next-y{right:15px}.layui-laydate-header i.laydate-next-m{right:45px}.laydate-set-ym{width:100%;text-align:center;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.laydate-time-text{cursor:default!important}.layui-laydate-content{position:relative;padding:10px;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.layui-laydate-content table{border-collapse:collapse;border-spacing:0}.layui-laydate-content td,.layui-laydate-content th{width:36px;height:30px;padding:5px;text-align:center}.layui-laydate-content td{position:relative;cursor:pointer}.laydate-day-mark{position:absolute;left:0;top:0;width:100%;height:100%;line-height:30px;font-size:12px;overflow:hidden}.laydate-day-mark::after{position:absolute;content:'';right:2px;top:2px;width:5px;height:5px;border-radius:50%}.layui-laydate-footer{position:relative;height:46px;line-height:26px;padding:10px 20px}.layui-laydate-footer span{margin-right:15px;display:inline-block;cursor:pointer;font-size:12px}.layui-laydate-footer span:hover{color:#5FB878}.laydate-footer-btns{position:absolute;right:10px;top:10px}.laydate-footer-btns span{height:26px;line-height:26px;margin:0 0 0 -1px;padding:0 10px;border:1px solid #C9C9C9;background-color:#fff;white-space:nowrap;vertical-align:top;border-radius:2px}.layui-laydate-list>li,.layui-laydate-range .layui-laydate-main{display:inline-block;vertical-align:middle}.layui-laydate-list{position:absolute;left:0;top:0;width:100%;height:100%;padding:10px;background-color:#fff}.layui-laydate-list>li{position:relative;width:33.3%;height:36px;line-height:36px;margin:3px 0;text-align:center;cursor:pointer}.laydate-month-list>li{width:25%;margin:17px 0}.laydate-time-list>li{height:100%;margin:0;line-height:normal;cursor:default}.laydate-time-list p{position:relative;top:-4px;line-height:29px}.laydate-time-list ol{height:181px;overflow:hidden}.laydate-time-list>li:hover ol{overflow-y:auto}.laydate-time-list ol li{width:130%;padding-left:33px;line-height:30px;text-align:left;cursor:pointer}.layui-laydate-hint{position:absolute;top:115px;left:50%;width:250px;margin-left:-125px;line-height:20px;padding:15px;text-align:center;font-size:12px}.layui-laydate-range{width:546px}.layui-laydate-range .laydate-main-list-0 .laydate-next-m,.layui-laydate-range .laydate-main-list-0 .laydate-next-y,.layui-laydate-range .laydate-main-list-1 .laydate-prev-m,.layui-laydate-range .laydate-main-list-1 .laydate-prev-y{display:none}.layui-laydate-range .laydate-main-list-1 .layui-laydate-content{border-left:1px solid #e2e2e2}.layui-laydate,.layui-laydate-hint{border:1px solid #d2d2d2;box-shadow:0 2px 4px rgba(0,0,0,.12);background-color:#fff;color:#666}.layui-laydate-header{border-bottom:1px solid #e2e2e2}.layui-laydate-header i:hover,.layui-laydate-header span:hover{color:#5FB878}.layui-laydate-content{border-top:none 0;border-bottom:none 0}.layui-laydate-content th{font-weight:400;color:#333}.layui-laydate-content td{color:#666}.layui-laydate-content td.laydate-selected{background-color:#00F7DE}.laydate-selected:hover{background-color:#00F7DE!important}.layui-laydate-content td:hover,.layui-laydate-list li:hover{background-color:#eaeaea;color:#333}.laydate-time-list li ol{margin:0;padding:0;border:1px solid #e2e2e2;border-left-width:0}.laydate-time-list li:first-child ol{border-left-width:1px}.laydate-time-list>li:hover{background:0 0}.layui-laydate-content .laydate-day-next,.layui-laydate-content .laydate-day-prev{color:#d2d2d2}.laydate-selected.laydate-day-next,.laydate-selected.laydate-day-prev{background-color:#f8f8f8!important}.layui-laydate-footer{border-top:1px solid #e2e2e2}.layui-laydate-hint{color:#FF5722}.laydate-day-mark::after{background-color:#5FB878}.layui-laydate-content td.layui-this .laydate-day-mark::after{display:none}.layui-laydate-footer span[lay-type=date]{color:#5FB878}.layui-laydate .layui-this{background-color:#009688!important;color:#fff!important}.layui-laydate .laydate-disabled,.layui-laydate .laydate-disabled:hover{background:0 0!important;color:#d2d2d2!important;cursor:not-allowed!important;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.laydate-theme-molv{border:none}.laydate-theme-molv.layui-laydate-range{width:548px}.laydate-theme-molv .layui-laydate-main{width:274px}.laydate-theme-molv .layui-laydate-header{border:none;background-color:#009688}.laydate-theme-molv .layui-laydate-header i,.laydate-theme-molv .layui-laydate-header span{color:#f6f6f6}.laydate-theme-molv .layui-laydate-header i:hover,.laydate-theme-molv .layui-laydate-header span:hover{color:#fff}.laydate-theme-molv .layui-laydate-content{border:1px solid #e2e2e2;border-top:none;border-bottom:none}.laydate-theme-molv .laydate-main-list-1 .layui-laydate-content{border-left:none}.laydate-theme-grid .laydate-month-list>li,.laydate-theme-grid .laydate-year-list>li,.laydate-theme-grid .layui-laydate-content td,.laydate-theme-grid .layui-laydate-content thead,.laydate-theme-molv .layui-laydate-footer{border:1px solid #e2e2e2}.laydate-theme-grid .laydate-selected,.laydate-theme-grid .laydate-selected:hover{background-color:#f2f2f2!important;color:#009688!important}.laydate-theme-grid .laydate-selected.laydate-day-next,.laydate-theme-grid .laydate-selected.laydate-day-prev{color:#d2d2d2!important}.laydate-theme-grid .laydate-month-list,.laydate-theme-grid .laydate-year-list{margin:1px 0 0 1px}.laydate-theme-grid .laydate-month-list>li,.laydate-theme-grid .laydate-year-list>li{margin:0 -1px -1px 0}.laydate-theme-grid .laydate-year-list>li{height:43px;line-height:43px}.laydate-theme-grid .laydate-month-list>li{height:71px;line-height:71px} -------------------------------------------------------------------------------- /static/layui/css/modules/layer/default/icon-ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/css/modules/layer/default/icon-ext.png -------------------------------------------------------------------------------- /static/layui/css/modules/layer/default/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/css/modules/layer/default/icon.png -------------------------------------------------------------------------------- /static/layui/css/modules/layer/default/layer.css: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | .layui-layer-imgbar,.layui-layer-imgtit a,.layui-layer-tab .layui-layer-title span,.layui-layer-title{text-overflow:ellipsis;white-space:nowrap}html #layuicss-layer{display:none;position:absolute;width:1989px}.layui-layer,.layui-layer-shade{position:fixed;_position:absolute;pointer-events:auto}.layui-layer-shade{top:0;left:0;width:100%;height:100%;_height:expression(document.body.offsetHeight+"px")}.layui-layer{-webkit-overflow-scrolling:touch;top:150px;left:0;margin:0;padding:0;background-color:#fff;-webkit-background-clip:content;border-radius:2px;box-shadow:1px 1px 50px rgba(0,0,0,.3)}.layui-layer-close{position:absolute}.layui-layer-content{position:relative}.layui-layer-border{border:1px solid #B2B2B2;border:1px solid rgba(0,0,0,.1);box-shadow:1px 1px 5px rgba(0,0,0,.2)}.layui-layer-load{background:url(loading-1.gif) center center no-repeat #eee}.layui-layer-ico{background:url(icon.png) no-repeat}.layui-layer-btn a,.layui-layer-dialog .layui-layer-ico,.layui-layer-setwin a{display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-move{display:none;position:fixed;*position:absolute;left:0;top:0;width:100%;height:100%;cursor:move;opacity:0;filter:alpha(opacity=0);background-color:#fff;z-index:2147483647}.layui-layer-resize{position:absolute;width:15px;height:15px;right:0;bottom:0;cursor:se-resize}.layer-anim{-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.3s;animation-duration:.3s}@-webkit-keyframes layer-bounceIn{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes layer-bounceIn{0%{opacity:0;-webkit-transform:scale(.5);-ms-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim-00{-webkit-animation-name:layer-bounceIn;animation-name:layer-bounceIn}@-webkit-keyframes layer-zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes layer-zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);-ms-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);-ms-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-01{-webkit-animation-name:layer-zoomInDown;animation-name:layer-zoomInDown}@-webkit-keyframes layer-fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes layer-fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);-ms-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}.layer-anim-02{-webkit-animation-name:layer-fadeInUpBig;animation-name:layer-fadeInUpBig}@-webkit-keyframes layer-zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes layer-zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);-ms-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);-ms-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-03{-webkit-animation-name:layer-zoomInLeft;animation-name:layer-zoomInLeft}@-webkit-keyframes layer-rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}@keyframes layer-rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);-ms-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);-ms-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}.layer-anim-04{-webkit-animation-name:layer-rollIn;animation-name:layer-rollIn}@keyframes layer-fadeIn{0%{opacity:0}100%{opacity:1}}.layer-anim-05{-webkit-animation-name:layer-fadeIn;animation-name:layer-fadeIn}@-webkit-keyframes layer-shake{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);transform:translateX(10px)}}@keyframes layer-shake{0%,100%{-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);-ms-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);-ms-transform:translateX(10px);transform:translateX(10px)}}.layer-anim-06{-webkit-animation-name:layer-shake;animation-name:layer-shake}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.layui-layer-title{padding:0 80px 0 20px;height:42px;line-height:42px;border-bottom:1px solid #eee;font-size:14px;color:#333;overflow:hidden;background-color:#F8F8F8;border-radius:2px 2px 0 0}.layui-layer-setwin{position:absolute;right:15px;*right:0;top:15px;font-size:0;line-height:initial}.layui-layer-setwin a{position:relative;width:16px;height:16px;margin-left:10px;font-size:12px;_overflow:hidden}.layui-layer-setwin .layui-layer-min cite{position:absolute;width:14px;height:2px;left:0;top:50%;margin-top:-1px;background-color:#2E2D3C;cursor:pointer;_overflow:hidden}.layui-layer-setwin .layui-layer-min:hover cite{background-color:#2D93CA}.layui-layer-setwin .layui-layer-max{background-position:-32px -40px}.layui-layer-setwin .layui-layer-max:hover{background-position:-16px -40px}.layui-layer-setwin .layui-layer-maxmin{background-position:-65px -40px}.layui-layer-setwin .layui-layer-maxmin:hover{background-position:-49px -40px}.layui-layer-setwin .layui-layer-close1{background-position:1px -40px;cursor:pointer}.layui-layer-setwin .layui-layer-close1:hover{opacity:.7}.layui-layer-setwin .layui-layer-close2{position:absolute;right:-28px;top:-28px;width:30px;height:30px;margin-left:0;background-position:-149px -31px;*right:-18px;_display:none}.layui-layer-setwin .layui-layer-close2:hover{background-position:-180px -31px}.layui-layer-btn{text-align:right;padding:0 15px 12px;pointer-events:auto;user-select:none;-webkit-user-select:none}.layui-layer-btn a{height:28px;line-height:28px;margin:5px 5px 0;padding:0 15px;border:1px solid #dedede;background-color:#fff;color:#333;border-radius:2px;font-weight:400;cursor:pointer;text-decoration:none}.layui-layer-btn a:hover{opacity:.9;text-decoration:none}.layui-layer-btn a:active{opacity:.8}.layui-layer-btn .layui-layer-btn0{border-color:#1E9FFF;background-color:#1E9FFF;color:#fff}.layui-layer-btn-l{text-align:left}.layui-layer-btn-c{text-align:center}.layui-layer-dialog{min-width:260px}.layui-layer-dialog .layui-layer-content{position:relative;padding:20px;line-height:24px;word-break:break-all;overflow:hidden;font-size:14px;overflow-x:hidden;overflow-y:auto}.layui-layer-dialog .layui-layer-content .layui-layer-ico{position:absolute;top:16px;left:15px;_left:-40px;width:30px;height:30px}.layui-layer-ico1{background-position:-30px 0}.layui-layer-ico2{background-position:-60px 0}.layui-layer-ico3{background-position:-90px 0}.layui-layer-ico4{background-position:-120px 0}.layui-layer-ico5{background-position:-150px 0}.layui-layer-ico6{background-position:-180px 0}.layui-layer-rim{border:6px solid #8D8D8D;border:6px solid rgba(0,0,0,.3);border-radius:5px;box-shadow:none}.layui-layer-msg{min-width:180px;border:1px solid #D3D4D3;box-shadow:none}.layui-layer-hui{min-width:100px;background-color:#000;filter:alpha(opacity=60);background-color:rgba(0,0,0,.6);color:#fff;border:none}.layui-layer-hui .layui-layer-content{padding:12px 25px;text-align:center}.layui-layer-dialog .layui-layer-padding{padding:20px 20px 20px 55px;text-align:left}.layui-layer-page .layui-layer-content{position:relative;overflow:auto}.layui-layer-iframe .layui-layer-btn,.layui-layer-page .layui-layer-btn{padding-top:10px}.layui-layer-nobg{background:0 0}.layui-layer-iframe iframe{display:block;width:100%}.layui-layer-loading{border-radius:100%;background:0 0;box-shadow:none;border:none}.layui-layer-loading .layui-layer-content{width:60px;height:24px;background:url(loading-0.gif) no-repeat}.layui-layer-loading .layui-layer-loading1{width:37px;height:37px;background:url(loading-1.gif) no-repeat}.layui-layer-ico16,.layui-layer-loading .layui-layer-loading2{width:32px;height:32px;background:url(loading-2.gif) no-repeat}.layui-layer-tips{background:0 0;box-shadow:none;border:none}.layui-layer-tips .layui-layer-content{position:relative;line-height:22px;min-width:12px;padding:8px 15px;font-size:12px;_float:left;border-radius:2px;box-shadow:1px 1px 3px rgba(0,0,0,.2);background-color:#000;color:#fff}.layui-layer-tips .layui-layer-close{right:-2px;top:-1px}.layui-layer-tips i.layui-layer-TipsG{position:absolute;width:0;height:0;border-width:8px;border-color:transparent;border-style:dashed;*overflow:hidden}.layui-layer-tips i.layui-layer-TipsB,.layui-layer-tips i.layui-layer-TipsT{left:5px;border-right-style:solid;border-right-color:#000}.layui-layer-tips i.layui-layer-TipsT{bottom:-8px}.layui-layer-tips i.layui-layer-TipsB{top:-8px}.layui-layer-tips i.layui-layer-TipsL,.layui-layer-tips i.layui-layer-TipsR{top:5px;border-bottom-style:solid;border-bottom-color:#000}.layui-layer-tips i.layui-layer-TipsR{left:-8px}.layui-layer-tips i.layui-layer-TipsL{right:-8px}.layui-layer-lan[type=dialog]{min-width:280px}.layui-layer-lan .layui-layer-title{background:#4476A7;color:#fff;border:none}.layui-layer-lan .layui-layer-btn{padding:5px 10px 10px;text-align:right;border-top:1px solid #E9E7E7}.layui-layer-lan .layui-layer-btn a{background:#fff;border-color:#E9E7E7;color:#333}.layui-layer-lan .layui-layer-btn .layui-layer-btn1{background:#C9C5C5}.layui-layer-molv .layui-layer-title{background:#009f95;color:#fff;border:none}.layui-layer-molv .layui-layer-btn a{background:#009f95;border-color:#009f95}.layui-layer-molv .layui-layer-btn .layui-layer-btn1{background:#92B8B1}.layui-layer-iconext{background:url(icon-ext.png) no-repeat}.layui-layer-prompt .layui-layer-input{display:block;width:230px;height:36px;margin:0 auto;line-height:30px;padding-left:10px;border:1px solid #e6e6e6;color:#333}.layui-layer-prompt textarea.layui-layer-input{width:300px;height:100px;line-height:20px;padding:6px 10px}.layui-layer-prompt .layui-layer-content{padding:20px}.layui-layer-prompt .layui-layer-btn{padding-top:0}.layui-layer-tab{box-shadow:1px 1px 50px rgba(0,0,0,.4)}.layui-layer-tab .layui-layer-title{padding-left:0;overflow:visible}.layui-layer-tab .layui-layer-title span{position:relative;float:left;min-width:80px;max-width:260px;padding:0 20px;text-align:center;overflow:hidden;cursor:pointer}.layui-layer-tab .layui-layer-title span.layui-this{height:43px;border-left:1px solid #eee;border-right:1px solid #eee;background-color:#fff;z-index:10}.layui-layer-tab .layui-layer-title span:first-child{border-left:none}.layui-layer-tabmain{line-height:24px;clear:both}.layui-layer-tabmain .layui-layer-tabli{display:none}.layui-layer-tabmain .layui-layer-tabli.layui-this{display:block}.layui-layer-photos{-webkit-animation-duration:.8s;animation-duration:.8s}.layui-layer-photos .layui-layer-content{overflow:hidden;text-align:center}.layui-layer-photos .layui-layer-phimg img{position:relative;width:100%;display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-imgbar,.layui-layer-imguide{display:none}.layui-layer-imgnext,.layui-layer-imgprev{position:absolute;top:50%;width:27px;_width:44px;height:44px;margin-top:-22px;outline:0;blr:expression(this.onFocus=this.blur())}.layui-layer-imgprev{left:10px;background-position:-5px -5px;_background-position:-70px -5px}.layui-layer-imgprev:hover{background-position:-33px -5px;_background-position:-120px -5px}.layui-layer-imgnext{right:10px;_right:8px;background-position:-5px -50px;_background-position:-70px -50px}.layui-layer-imgnext:hover{background-position:-33px -50px;_background-position:-120px -50px}.layui-layer-imgbar{position:absolute;left:0;bottom:0;width:100%;height:32px;line-height:32px;background-color:rgba(0,0,0,.8);background-color:#000\9;filter:Alpha(opacity=80);color:#fff;overflow:hidden;font-size:0}.layui-layer-imgtit *{display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:12px}.layui-layer-imgtit a{max-width:65%;overflow:hidden;color:#fff}.layui-layer-imgtit a:hover{color:#fff;text-decoration:underline}.layui-layer-imgtit em{padding-left:10px;font-style:normal}@-webkit-keyframes layer-bounceOut{100%{opacity:0;-webkit-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes layer-bounceOut{100%{opacity:0;-webkit-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);-ms-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim-close{-webkit-animation-name:layer-bounceOut;animation-name:layer-bounceOut;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.2s;animation-duration:.2s}@media screen and (max-width:1100px){.layui-layer-iframe{overflow-y:auto;-webkit-overflow-scrolling:touch}} -------------------------------------------------------------------------------- /static/layui/css/modules/layer/default/loading-0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/css/modules/layer/default/loading-0.gif -------------------------------------------------------------------------------- /static/layui/css/modules/layer/default/loading-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/css/modules/layer/default/loading-1.gif -------------------------------------------------------------------------------- /static/layui/css/modules/layer/default/loading-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/css/modules/layer/default/loading-2.gif -------------------------------------------------------------------------------- /static/layui/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/font/iconfont.eot -------------------------------------------------------------------------------- /static/layui/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/font/iconfont.ttf -------------------------------------------------------------------------------- /static/layui/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/font/iconfont.woff -------------------------------------------------------------------------------- /static/layui/font/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/font/iconfont.woff2 -------------------------------------------------------------------------------- /static/layui/images/face/0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/0.gif -------------------------------------------------------------------------------- /static/layui/images/face/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/1.gif -------------------------------------------------------------------------------- /static/layui/images/face/10.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/10.gif -------------------------------------------------------------------------------- /static/layui/images/face/11.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/11.gif -------------------------------------------------------------------------------- /static/layui/images/face/12.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/12.gif -------------------------------------------------------------------------------- /static/layui/images/face/13.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/13.gif -------------------------------------------------------------------------------- /static/layui/images/face/14.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/14.gif -------------------------------------------------------------------------------- /static/layui/images/face/15.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/15.gif -------------------------------------------------------------------------------- /static/layui/images/face/16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/16.gif -------------------------------------------------------------------------------- /static/layui/images/face/17.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/17.gif -------------------------------------------------------------------------------- /static/layui/images/face/18.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/18.gif -------------------------------------------------------------------------------- /static/layui/images/face/19.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/19.gif -------------------------------------------------------------------------------- /static/layui/images/face/2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/2.gif -------------------------------------------------------------------------------- /static/layui/images/face/20.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/20.gif -------------------------------------------------------------------------------- /static/layui/images/face/21.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/21.gif -------------------------------------------------------------------------------- /static/layui/images/face/22.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/22.gif -------------------------------------------------------------------------------- /static/layui/images/face/23.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/23.gif -------------------------------------------------------------------------------- /static/layui/images/face/24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/24.gif -------------------------------------------------------------------------------- /static/layui/images/face/25.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/25.gif -------------------------------------------------------------------------------- /static/layui/images/face/26.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/26.gif -------------------------------------------------------------------------------- /static/layui/images/face/27.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/27.gif -------------------------------------------------------------------------------- /static/layui/images/face/28.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/28.gif -------------------------------------------------------------------------------- /static/layui/images/face/29.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/29.gif -------------------------------------------------------------------------------- /static/layui/images/face/3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/3.gif -------------------------------------------------------------------------------- /static/layui/images/face/30.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/30.gif -------------------------------------------------------------------------------- /static/layui/images/face/31.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/31.gif -------------------------------------------------------------------------------- /static/layui/images/face/32.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/32.gif -------------------------------------------------------------------------------- /static/layui/images/face/33.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/33.gif -------------------------------------------------------------------------------- /static/layui/images/face/34.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/34.gif -------------------------------------------------------------------------------- /static/layui/images/face/35.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/35.gif -------------------------------------------------------------------------------- /static/layui/images/face/36.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/36.gif -------------------------------------------------------------------------------- /static/layui/images/face/37.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/37.gif -------------------------------------------------------------------------------- /static/layui/images/face/38.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/38.gif -------------------------------------------------------------------------------- /static/layui/images/face/39.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/39.gif -------------------------------------------------------------------------------- /static/layui/images/face/4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/4.gif -------------------------------------------------------------------------------- /static/layui/images/face/40.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/40.gif -------------------------------------------------------------------------------- /static/layui/images/face/41.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/41.gif -------------------------------------------------------------------------------- /static/layui/images/face/42.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/42.gif -------------------------------------------------------------------------------- /static/layui/images/face/43.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/43.gif -------------------------------------------------------------------------------- /static/layui/images/face/44.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/44.gif -------------------------------------------------------------------------------- /static/layui/images/face/45.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/45.gif -------------------------------------------------------------------------------- /static/layui/images/face/46.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/46.gif -------------------------------------------------------------------------------- /static/layui/images/face/47.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/47.gif -------------------------------------------------------------------------------- /static/layui/images/face/48.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/48.gif -------------------------------------------------------------------------------- /static/layui/images/face/49.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/49.gif -------------------------------------------------------------------------------- /static/layui/images/face/5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/5.gif -------------------------------------------------------------------------------- /static/layui/images/face/50.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/50.gif -------------------------------------------------------------------------------- /static/layui/images/face/51.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/51.gif -------------------------------------------------------------------------------- /static/layui/images/face/52.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/52.gif -------------------------------------------------------------------------------- /static/layui/images/face/53.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/53.gif -------------------------------------------------------------------------------- /static/layui/images/face/54.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/54.gif -------------------------------------------------------------------------------- /static/layui/images/face/55.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/55.gif -------------------------------------------------------------------------------- /static/layui/images/face/56.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/56.gif -------------------------------------------------------------------------------- /static/layui/images/face/57.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/57.gif -------------------------------------------------------------------------------- /static/layui/images/face/58.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/58.gif -------------------------------------------------------------------------------- /static/layui/images/face/59.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/59.gif -------------------------------------------------------------------------------- /static/layui/images/face/6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/6.gif -------------------------------------------------------------------------------- /static/layui/images/face/60.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/60.gif -------------------------------------------------------------------------------- /static/layui/images/face/61.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/61.gif -------------------------------------------------------------------------------- /static/layui/images/face/62.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/62.gif -------------------------------------------------------------------------------- /static/layui/images/face/63.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/63.gif -------------------------------------------------------------------------------- /static/layui/images/face/64.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/64.gif -------------------------------------------------------------------------------- /static/layui/images/face/65.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/65.gif -------------------------------------------------------------------------------- /static/layui/images/face/66.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/66.gif -------------------------------------------------------------------------------- /static/layui/images/face/67.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/67.gif -------------------------------------------------------------------------------- /static/layui/images/face/68.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/68.gif -------------------------------------------------------------------------------- /static/layui/images/face/69.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/69.gif -------------------------------------------------------------------------------- /static/layui/images/face/7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/7.gif -------------------------------------------------------------------------------- /static/layui/images/face/70.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/70.gif -------------------------------------------------------------------------------- /static/layui/images/face/71.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/71.gif -------------------------------------------------------------------------------- /static/layui/images/face/8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/8.gif -------------------------------------------------------------------------------- /static/layui/images/face/9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libaibuaidufu/Fastapi-scheduler/dcc837de05abacaf17c7e3cd95e07a73a99cab17/static/layui/images/face/9.gif -------------------------------------------------------------------------------- /static/layui/lay/modules/carousel.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(e){"use strict";var i=layui.$,n=(layui.hint(),layui.device(),{config:{},set:function(e){var n=this;return n.config=i.extend({},n.config,e),n},on:function(e,i){return layui.onevent.call(this,t,e,i)}}),t="carousel",a="layui-this",l=">*[carousel-item]>*",o="layui-carousel-left",r="layui-carousel-right",d="layui-carousel-prev",s="layui-carousel-next",u="layui-carousel-arrow",c="layui-carousel-ind",m=function(e){var t=this;t.config=i.extend({},t.config,n.config,e),t.render()};m.prototype.config={width:"600px",height:"280px",full:!1,arrow:"hover",indicator:"inside",autoplay:!0,interval:3e3,anim:"",trigger:"click",index:0},m.prototype.render=function(){var e=this,n=e.config;n.elem=i(n.elem),n.elem[0]&&(e.elemItem=n.elem.find(l),n.index<0&&(n.index=0),n.index>=e.elemItem.length&&(n.index=e.elemItem.length-1),n.interval<800&&(n.interval=800),n.full?n.elem.css({position:"fixed",width:"100%",height:"100%",zIndex:9999}):n.elem.css({width:n.width,height:n.height}),n.elem.attr("lay-anim",n.anim),e.elemItem.eq(n.index).addClass(a),e.elemItem.length<=1||(e.indicator(),e.arrow(),e.autoplay(),e.events()))},m.prototype.reload=function(e){var n=this;clearInterval(n.timer),n.config=i.extend({},n.config,e),n.render()},m.prototype.prevIndex=function(){var e=this,i=e.config,n=i.index-1;return n<0&&(n=e.elemItem.length-1),n},m.prototype.nextIndex=function(){var e=this,i=e.config,n=i.index+1;return n>=e.elemItem.length&&(n=0),n},m.prototype.addIndex=function(e){var i=this,n=i.config;e=e||1,n.index=n.index+e,n.index>=i.elemItem.length&&(n.index=0)},m.prototype.subIndex=function(e){var i=this,n=i.config;e=e||1,n.index=n.index-e,n.index<0&&(n.index=i.elemItem.length-1)},m.prototype.autoplay=function(){var e=this,i=e.config;i.autoplay&&(clearInterval(e.timer),e.timer=setInterval(function(){e.slide()},i.interval))},m.prototype.arrow=function(){var e=this,n=e.config,t=i(['",'"].join(""));n.elem.attr("lay-arrow",n.arrow),n.elem.find("."+u)[0]&&n.elem.find("."+u).remove(),n.elem.append(t),t.on("click",function(){var n=i(this),t=n.attr("lay-type");e.slide(t)})},m.prototype.indicator=function(){var e=this,n=e.config,t=e.elemInd=i(['
")}}),e(n).parents("form").on("submit",function(){var t=c.html();8==l.ie&&(t=t.replace(/<.+>/g,function(t){return t.toLowerCase()})),n.value=t}),c.on("paste",function(e){r.execCommand("formatBlock",!1,"
"),setTimeout(function(){f.call(t,c),n.value=c.html()},100)})},f=function(t){var i=this;i.document;t.find("*[style]").each(function(){var t=this.style.textAlign;this.removeAttribute("style"),e(this).css({"text-align":t||""})}),t.find("table").addClass("layui-table"),t.find("script,link").remove()},m=function(t){return t.selection?t.selection.createRange():t.getSelection().getRangeAt(0)},p=function(t){return t.endContainer||t.parentElement().childNodes[0]},v=function(t,i,a){var l=this.document,n=document.createElement(t);for(var o in i)n.setAttribute(o,i[o]);if(n.removeAttribute("text"),l.selection){var r=a.text||i.text;if("a"===t&&!r)return;r&&(n.innerHTML=r),a.pasteHTML(e(n).prop("outerHTML")),a.select()}else{var r=a.toString()||i.text;if("a"===t&&!r)return;r&&(n.innerHTML=r),a.deleteContents(),a.insertNode(n)}},h=function(t,i){var a=this.document,l="layedit-tool-active",n=p(m(a)),o=function(e){return t.find(".layedit-tool-"+e)};i&&i[i.hasClass(l)?"removeClass":"addClass"](l),t.find(">i").removeClass(l),o("unlink").addClass(r),e(n).parents().each(function(){var t=this.tagName.toLowerCase(),e=this.style.textAlign;"b"!==t&&"strong"!==t||o("b").addClass(l),"i"!==t&&"em"!==t||o("i").addClass(l),"u"===t&&o("u").addClass(l),"strike"===t&&o("d").addClass(l),"p"===t&&("center"===e?o("center").addClass(l):"right"===e?o("right").addClass(l):o("left").addClass(l)),"a"===t&&(o("link").addClass(l),o("unlink").removeClass(r))})},g=function(t,a,l){var n=t.document,o=e(n.body),c={link:function(i){var a=p(i),l=e(a).parent();b.call(o,{href:l.attr("href"),target:l.attr("target")},function(e){var a=l[0];"A"===a.tagName?a.href=e.url:v.call(t,"a",{target:e.target,href:e.url,text:e.url},i)})},unlink:function(t){n.execCommand("unlink")},face:function(e){x.call(this,function(i){v.call(t,"img",{src:i.src,alt:i.alt},e)})},image:function(a){var n=this;layui.use("upload",function(o){var r=l.uploadImage||{};o.render({url:r.url,method:r.type,elem:e(n).find("input")[0],done:function(e){0==e.code?(e.data=e.data||{},v.call(t,"img",{src:e.data.src,alt:e.data.title},a)):i.msg(e.msg||"上传失败")}})})},code:function(e){k.call(o,function(i){v.call(t,"pre",{text:i.code,"lay-lang":i.lang},e)})},help:function(){i.open({type:2,title:"帮助",area:["600px","380px"],shadeClose:!0,shade:.1,skin:"layui-layer-msg",content:["http://www.layui.com/about/layedit/help.html","no"]})}},s=a.find(".layui-layedit-tool"),u=function(){var i=e(this),a=i.attr("layedit-event"),l=i.attr("lay-command");if(!i.hasClass(r)){o.focus();var u=m(n);u.commonAncestorContainer;l?(n.execCommand(l),/justifyLeft|justifyCenter|justifyRight/.test(l)&&n.execCommand("formatBlock",!1,"
"),setTimeout(function(){o.focus()},10)):c[a]&&c[a].call(this,u),h.call(t,s,i)}},d=/image/;s.find(">i").on("mousedown",function(){var t=e(this),i=t.attr("layedit-event");d.test(i)||u.call(this)}).on("click",function(){var t=e(this),i=t.attr("layedit-event");d.test(i)&&u.call(this)}),o.on("click",function(){h.call(t,s),i.close(x.index)})},b=function(t,e){var l=this,n=i.open({type:1,id:"LAY_layedit_link",area:"350px",shade:.05,shadeClose:!0,moveType:1,title:"超链接",skin:"layui-layer-msg",content:['
'+(t||"")+"
");e.find("."+d)[0]&&e.find("."+d).remove(),t.replace(/\s/g,"")&&e.append(n)},x.prototype.setValue=function(){var e=this,t=e.config,n=[];return e.layBox.eq(1).find("."+y+' input[type="checkbox"]').each(function(){var e=a(this).data("hide");e||n.push(this.value)}),t.value=n,e},x.prototype.parseData=function(e){var t=this,n=t.config,i=[];return layui.each(n.data,function(t,l){l=("function"==typeof n.parseData?n.parseData(l):l)||l,i.push(l=a.extend({},l)),layui.each(n.value,function(e,a){a==l.value&&(l.selected=!0)}),e&&e(l)}),n.data=i,t},x.prototype.getData=function(e){var a=this,t=a.config,n=[];return a.setValue(),layui.each(e||t.value,function(e,a){layui.each(t.data,function(e,t){delete t.selected,a==t.value&&n.push(t)})}),n},x.prototype.events=function(){var e=this,t=e.config;e.elem.on("click",'input[lay-filter="layTransferCheckbox"]+',function(){var t=a(this).prev(),n=t[0].checked,i=t.parents("."+s).eq(0).find("."+y);t[0].disabled||("all"===t.attr("lay-type")&&i.find('input[type="checkbox"]').each(function(){this.disabled||(this.checked=n)}),e.renderCheckBtn({stopNone:!0}))}),e.layBtn.on("click",function(){var n=a(this),i=n.data("index"),l=e.layBox.eq(i),r=[];if(!n.hasClass(o)){e.layBox.eq(i).each(function(t){var n=a(this),i=n.find("."+y);i.children("li").each(function(){var t=a(this),n=t.find('input[type="checkbox"]'),i=n.data("hide");n[0].checked&&!i&&(n[0].checked=!1,l.siblings("."+s).find("."+y).append(t.clone()),t.remove(),r.push(n[0].value)),e.setValue()})}),e.renderCheckBtn();var c=l.siblings("."+s).find("."+h+" input");""===c.val()||c.trigger("keyup"),t.onchange&&t.onchange(e.getData(r),i)}}),e.laySearch.find("input").on("keyup",function(){var n=this.value,i=a(this).parents("."+h).eq(0).siblings("."+y),l=i.children("li");l.each(function(){var e=a(this),t=e.find('input[type="checkbox"]'),i=t[0].title.indexOf(n)!==-1;e[i?"removeClass":"addClass"](c),t.data("hide",!i)}),e.renderCheckBtn();var r=l.length===i.children("li."+c).length;e.noneView(i,r?t.text.searchNone:"")})},r.that={},r.config={},l.reload=function(e,a){var t=r.that[e];return t.reload(a),r.call(t)},l.getData=function(e){var a=r.that[e];return a.getData()},l.render=function(e){var a=new x(e);return r.call(a)},e(i,l)}); -------------------------------------------------------------------------------- /static/layui/lay/modules/tree.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;layui.define("form",function(e){"use strict";var i=layui.$,a=layui.form,n=layui.layer,t="tree",r={config:{},index:layui[t]?layui[t].index+1e4:0,set:function(e){var a=this;return a.config=i.extend({},a.config,e),a},on:function(e,i){return layui.onevent.call(this,t,e,i)}},l=function(){var e=this,i=e.config,a=i.id||e.index;return l.that[a]=e,l.config[a]=i,{config:i,reload:function(i){e.reload.call(e,i)},getChecked:function(){return e.getChecked.call(e)},setChecked:function(i){return e.setChecked.call(e,i)}}},c="layui-hide",d="layui-disabled",s="layui-tree-set",o="layui-tree-iconClick",h="layui-icon-addition",u="layui-icon-subtraction",p="layui-tree-entry",f="layui-tree-main",y="layui-tree-txt",v="layui-tree-pack",C="layui-tree-spread",k="layui-tree-setLineShort",m="layui-tree-showLine",x="layui-tree-lineExtend",b=function(e){var a=this;a.index=++r.index,a.config=i.extend({},a.config,r.config,e),a.render()};b.prototype.config={data:[],showCheckbox:!1,showLine:!0,accordion:!1,onlyIconControl:!1,isJump:!1,edit:!1,text:{defaultNodeName:"未命名",none:"无数据"}},b.prototype.reload=function(e){var a=this;layui.each(e,function(e,i){i.constructor===Array&&delete a.config[e]}),a.config=i.extend(!0,{},a.config,e),a.render()},b.prototype.render=function(){var e=this,a=e.config;e.checkids=[];var n=i('');e.tree(n);var t=a.elem=i(a.elem);if(t[0]){if(e.key=a.id||e.index,e.elem=n,e.elemNone=i('id | 54 |任务名称 | 55 |任务描述 | 56 |脚本位置 | 57 |任务计划 | 58 |操作 | 59 |
---|---|---|---|---|---|
{{ data.id }} | 65 |{{ data.schedule_name }} | 66 |{{ data.schedule_desc }} | 67 |{{ data.script_name }} | 68 |{{ data.task_plan }} | 69 |70 | 72 | 74 | 77 | | 78 |
id | 100 |任务名称 | 101 |状态 | 102 |任务执行计划 | 103 |下一次执行时间 | 104 |脚本名称 | 105 |操作 | 106 |
---|---|---|---|---|---|---|
{{ data.id }} | 112 |{{ data.name }} | 113 |{{ data.pending }} | 114 |{{ data.cron_info }} | 115 |{{ data.next_run_time }} | 116 |{{ data.func_ref }} | 117 | 118 |119 | 123 | 126 | 129 | 130 | 134 | 135 | | 136 |