<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12
      TOP

      基于 dash + feffery-antd-components 單文件實現定時任務調度平臺

      實現效果

      列表展示定時任務

      支持搜索, 添加, 啟用. 停用. 編輯, 和刪除功能

      新增/編輯/刪除定時任務

       支持 周期/日期/corn 三種調度模式

      支持 python/shell/api 三種任務類型

      刪除進行二次確認后即可刪除

      觸發記錄查看

      支持詳情查看, 以及一些便捷 搜索/篩選/分頁功能

      日志詳情

      可查看任務的執行結果, 展示原始執行腳本, 和標準以及異常輸出

      完整代碼

      """
      依賴模塊
      dash>=2.18.2
      feffery_antd_components>=0.3.11
      feffery_dash_utils>=0.1.5
      feffery_utils_components>=0.2.0rc25
      feffery-markdown-components
      peewee
      flask
      requests~=2.32.3
      apscheduler
      """ import io import sys import dash import requests import datetime import subprocess from dash import html import feffery_antd_components as fac from feffery_dash_utils.style_utils import style from dash.dependencies import Input, Output, State from apscheduler.triggers.cron import CronTrigger from apscheduler.schedulers.background import BackgroundScheduler from peewee import Model, MySQLDatabase, TextField, CharField, BooleanField, DateTimeField, IntegerField # ------------------------------------------- 模型類 ------------------------------------------- # 數據庫連接 db = MySQLDatabase( 'apex', user='root', password='123456', host='17.0.186.117', port=3306 ) # 定時任務模型 class Task(Model): task_name = CharField(null=True) # 任務名 task_type = CharField(null=True) # 腳本類型: Python 腳本 / Shell 腳本 / API 調用 command = TextField(null=True) # 腳本命令: 腳本內容 / API 地址 schedule_type = CharField(null=True) # 調用類型: 周期調用, date, cron schedule_config = CharField(null=True) # 調用配置: 調用秒數 / 調用時間 / cron表達式 is_active = BooleanField(default=False) # 是否激活, 默認不激活 class Meta: database = db # 定時任務觸發結果模型 class TaskRecord(Model): task_id = IntegerField(null=True) # 任務id task_type = CharField(null=True) # 腳本類型: Python 腳本 / Shell 腳本 / API 調用 command = TextField(null=True) # 腳本命令: 腳本內容 / API 地址 run_date = DateTimeField(null=True) # 運行日期 result_type = CharField(null=True) # 調用狀態: success / error output = TextField(null=True) # 輸出正確內容 error = TextField(null=True) # 輸出異常內容 class Meta: database = db # ------------------------------------------- 定時任務 ------------------------------------------- class Scheduler: def __init__(self): self.scheduler = BackgroundScheduler() self.scheduler.start() self.load() @staticmethod def execute_task(task): output = "" error = "" if task.task_type == 'Python 腳本': try: old_stdout = sys.stdout result = io.StringIO() sys.stdout = result exec(task.command, {}) # 定義一個命名空間避免影響當前環境的變量 sys.stdout = old_stdout output = result.getvalue() except Exception as e: error = str(e) elif task.task_type == 'Shell 腳本': try: # 捕獲標準輸出和標準錯誤 process = subprocess.run( task.command, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) output = process.stdout except subprocess.CalledProcessError as e: error = e.stderr elif task.task_type == 'API 調用': try: response = requests.get(task.command) response.raise_for_status() # 檢查響應狀態碼 output = response.json() if response.headers.get( 'Content-Type') == 'application/json' else response.text except requests.RequestException as e: error = str(e) output and print(output) # 打印正常輸出 error and print(error) # 打印異常輸出 TaskRecord.create( task_id=task.id, task_type=task.task_type, command=task.command, run_date=datetime.datetime.now(), result_type="error" if error else "success", output=output, error=error, ) def create(self, task): if task.schedule_type == '周期調用': self.scheduler.add_job( self.execute_task, 'interval', seconds=int(task.schedule_config), args=[task], id=f"{task.id}") elif task.schedule_type == '日期調用(單次)': # str -> datetime run_date = datetime.datetime.strptime(task.schedule_config, "%Y-%m-%d %H:%M:%S") self.scheduler.add_job( self.execute_task, 'date', run_date=run_date, args=[task], id=f"{task.id}") elif task.schedule_type == 'Cron 調用': self.scheduler.add_job( self.execute_task, CronTrigger.from_crontab(task.schedule_config), args=[task], id=f"{task.id}") def destroy(self, task=None, task_id=None): if task_id: self.scheduler.remove_job(job_id=f"{task_id}") else: self.scheduler.remove_job(job_id=f"{task.id}") def load(self): for task in Task.select().where(Task.is_active == True): self.create(task) # ------------------------------------------- 操作入口 ------------------------------------------- class TaskHandler: def __init__(self): # 初始化數據庫 db.connect() # 初始化定時任務調度器 self.scheduler = Scheduler() # 創建 dash 應用 self.app = dash.Dash(__name__) # 定義標簽骨架 self.layout() # 加載回調 self.register_callbacks() @staticmethod def reset_db(): db.drop_tables([Task, TaskRecord]) # 按需重建數據庫 db.create_tables([Task, TaskRecord]) # 按需重建數據庫 @staticmethod def make_tabel_data(query): tasks = [] for i in query: tasks.append({ 'id': i.id, 'task_name': i.task_name, 'task_type': i.task_type, 'command': i.command, 'schedule_type': i.schedule_type, 'schedule_config': i.schedule_config, "is_active": { 'checked': i.is_active, 'checkedChildren': '啟用', 'unCheckedChildren': '停用', }, 'del_row': {'content': "刪除", 'type': 'dashed', 'danger': True}, 'edit_row': {'content': "編輯", 'type': 'default', "color": "blue"}, 'log_row': {'content': f"查看 ({len(TaskRecord.select().where(TaskRecord.task_id == i.id))})", 'type': 'dashed', "color": "blue"} }) return tasks def layout(self): self.app.layout = html.Div([ # 頁眉 fac.AntdFlex([ fac.AntdSpace( [ # logo html.Img( src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwM" "C9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWx" "sPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva" "2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWN" "pZGUgbHVjaWRlLWNsaXBib2FyZC1saXN0Ij48cmVjdCB3aWR0aD0iOCIgaGVpZ2h0PSI0Ii" "B4PSI4IiB5PSIyIiByeD0iMSIgcnk9IjEiLz48cGF0aCBkPSJNMTYgNGgyYTIgMiAwIDAgM" "SAyIDJ2MTRhMiAyIDAgMCAxLTIgMkg2YTIgMiAwIDAgMS0yLTJWNmEyIDIgMCAwIDEgMi0y" "aDIiLz48cGF0aCBkPSJNMTIgMTFoNCIvPjxwYXRoIGQ9Ik0xMiAxNmg0Ii8+PHBhdGggZD0" "iTTggMTFoLjAxIi8+PHBhdGggZD0iTTggMTZoLjAxIi8+PC9zdmc+", height=25, style=style(position="relative", top=3) ), fac.AntdText("定時任務調度腳本", strong=True, style=style(fontSize=27)), fac.AntdText("v1.0.0", className="global-help-text", style=style(fontSize=20)), ], align="baseline", size=5, id="core-header-title", ), fac.AntdSpace( [ fac.AntdButton( "添加任務", id="task-add", icon=fac.AntdIcon(icon="md-add", style=style(marginTop=4)), variant="outlined", motionType='happy-work', style=style(color="grey", marginRight=10)), fac.AntdInput( id="task-search", placeholder='搜索任務名', mode='search', style=style(width=500), allowClear=True)]), ], justify="space-between", style=style(padding=10)), # 新增 / 編輯抽屜 fac.AntdDrawer( fac.AntdForm( id='task-form', children=[ fac.AntdFormItem( label='任務名', children=fac.AntdInput(id='task-name') ), fac.AntdFormItem( label='調度類型', children=fac.AntdRadioGroup( id='schedule-type', options=[ {'label': '周期調用', 'value': '周期調用'}, {'label': '日期調用(單次)', 'value': '日期調用(單次)'}, {'label': 'Cron 調用', 'value': 'Cron 調用'} ], optionType='button', buttonStyle='solid', defaultValue='周期調用' ) ), fac.AntdFormItem( id="schedule-config-form-item", children=[ fac.AntdInputNumber(id='config-input-cycle', min=10, step=10, defaultValue=10, style=style(display="none")), fac.AntdDatePicker(id='config-input-date', showTime=True, style=style(display="none")), fac.AntdInput(id='config-input-corn', style=style(display="none")), ] ), fac.AntdFormItem( label='任務類型', children=fac.AntdRadioGroup( id='task-type', options=[ {'label': 'Python 腳本', 'value': 'Python 腳本'}, {'label': 'Shell 腳本', 'value': 'Shell 腳本'}, {'label': 'API 調用', 'value': 'API 調用'} ], optionType='button', buttonStyle='solid', defaultValue="Python 腳本" ), ), fac.AntdFormItem( label='命令', id='command-form-item', children=fac.AntdInput(id='command', mode="text-area", ) ), fac.AntdFormItem( children=fac.AntdButton('保存任務', id='task-form-button', type='primary') ) ] ), id=f'task-drawer', title='新增任務', width="25vw", ), # 觸發記錄抽屜 fac.AntdDrawer( [ fac.AntdFlex([ fac.AntdButton( "僅異常", id="res-error", icon=fac.AntdIcon(icon="antd-info-circle", style=style(marginTop=0)), variant="outlined", motionType='happy-work', style=style(color="grey", marginRight=10)), fac.AntdButton( "最近7日", id="res-7-day", icon=fac.AntdIcon(icon="antd-search", style=style(marginTop=0)), variant="outlined", motionType='happy-work', style=style(color="grey", marginRight=10)), fac.AntdButton( "最近30日", id="res-30-day", icon=fac.AntdIcon(icon="antd-search", style=style(marginTop=0)), variant="outlined", motionType='happy-work', style=style(color="grey", marginRight=10)), fac.AntdDateRangePicker( id="res-dt", placeholder=['開始日期時間', '結束日期時間'], showTime=True, needConfirm=True, style=style(marginRight=10) ), fac.AntdInput( id="res-search", placeholder='模糊查詢', mode='search', style=style(width=400), allowClear=True) ], justify="flex-start", style=style(padding=15)), fac.AntdTable( id='res-table', columns=[ {'title': '編號', 'dataIndex': 'id', "width": "5%"}, {'title': "狀態", 'dataIndex': "result_type", "width": "30%", 'renderOptions': {'renderType': 'status-badge'}}, {'title': '觸發時間', 'dataIndex': 'run_date', "width": "20%"}, {'title': "查看日志", 'dataIndex': 'detail_log', 'renderOptions': {'renderType': 'button'}, "width": "5%"}, ], data=[], pagination={ 'total': 0, 'current': 1, 'pageSize': 10, 'showSizeChanger': True, 'pageSizeOptions': [10, 20, 30], 'position': 'bottomCenter', 'showQuickJumper': True, } ) ], id=f'log-drawer', title='觸發記錄', width="60vw", ), # 查看觸發記錄詳細內容抽屜 fac.AntdDrawer( id=f'log-detail-drawer', title='輸出日志', width="50vw", ), # 列表 fac.AntdTable( id='task-table', columns=[ {'title': '任務名字', 'dataIndex': 'task_name'}, {'title': '任務類型', 'dataIndex': 'task_type'}, {'title': '調度類型', 'dataIndex': 'schedule_type'}, {'title': '調度配置', 'dataIndex': 'schedule_config'}, {'title': "狀態", 'dataIndex': "is_active", 'renderOptions': {'renderType': 'switch'}}, {'title': "觸發記錄", 'dataIndex': 'log_row', 'renderOptions': {'renderType': 'button'}, "width": "5%"}, {'title': "操作", 'dataIndex': 'edit_row', 'renderOptions': {'renderType': 'button'}, "width": "5%"}, {'title': "刪除", 'dataIndex': 'del_row', "width": "5%", 'renderOptions': { 'renderType': 'button', 'renderButtonPopConfirmProps': {'title': '確認刪除?', 'okText': '確認', 'cancelText': '取消'}, }}, ], data=[], pagination={ 'total': len(Task.select()), 'current': 1, 'pageSize': 10, 'showSizeChanger': True, 'pageSizeOptions': [10, 20, 30], 'position': 'bottomCenter', 'showQuickJumper': True, } ) ], style=style(padding=10)) def register_callbacks(self): # =========================== 渲染 定時任務列表 =========================== @self.app.callback(Output(f'task-table', 'data'), Output(f'task-table', 'pagination'), Input(f'task-table', 'pagination'), Input(f'task-search', 'value')) def table_list_show(pagination, s_value): query = Task.select() query = query.paginate(pagination['current'], pagination['pageSize']) if s_value: query = query.where((Task.task_name.contains(s_value))) return self.make_tabel_data(query), {**pagination, 'total': len(query)} # =========================== 點擊 "添加任務" , 彈出抽屜, 更新內容 =========================== @self.app.callback(Input('task-add', 'nClicks')) def open_drawer(_): # 更新相關文本展示 dash.set_props("task-form-button", {"children": "創建保存"}) dash.set_props("task-drawer", {"title": "新增任務"}) # 更新表單value dash.set_props("task-name", {"value": None}) dash.set_props("task-type", {"value": "Python 腳本"}) dash.set_props("command", {"value": None}) dash.set_props("config-input-cycle", {"value": None}) dash.set_props("config-input-date", {"value": None}) dash.set_props("config-input-corn", {"value": None}) dash.set_props("schedule-type", {"value": "周期調用"}) # 彈出抽屜 dash.set_props("task-drawer", {"visible": True}) # =========================== 渲染 按鈕按下改變顯示效果 =========================== @self.app.callback(Output(f'res-error', 'color'), Input(f'res-error', 'nClicks'), State(f'res-error', 'color')) def button_variant_change(_, color): if color == "default": dash.set_props("res-error", {"style": style(marginRight=10)}) return "primary" dash.set_props("res-error", {"style": style(color="grey", marginRight=10)}) return "default" @self.app.callback(Output(f'res-7-day', 'color'), Input(f'res-7-day', 'nClicks'), State(f'res-7-day', 'color')) def button_variant_change(_, color): if color == "default": dash.set_props("res-7-day", {"style": style(marginRight=10)}) return "primary" dash.set_props("res-7-day", {"style": style(color="grey", marginRight=10)}) return "default" @self.app.callback(Output(f'res-30-day', 'color'), Input(f'res-30-day', 'nClicks'), State(f'res-30-day', 'color')) def button_variant_change(_, color): if color == "default": dash.set_props("res-30-day", {"style": style(marginRight=10)}) return "primary" dash.set_props("res-30-day", {"style": style(color="grey", marginRight=10)}) return "default" # =========================== 渲染 觸發記錄列表 =========================== @self.app.callback(Output(f'res-table', 'data'), Output(f'res-table', 'pagination'), Input(f'res-search', 'value'), Input(f'res-error', 'color'), Input(f'res-7-day', 'color'), Input(f'res-30-day', 'color'), Input(f'res-dt', 'value'), State(f'task-table', 'recentlyButtonClickedRow'), State(f'res-table', 'pagination'), prevent_initial_call=True) def table_list_show(s_value, error_color, day_7_click, day_30_click, dt_range, row_data, pagination): if row_data: query = TaskRecord.select().where(TaskRecord.task_id == row_data["id"]) if s_value: query = query.where(TaskRecord.output.contains(s_value) | TaskRecord.error.contains(s_value)) if error_color == "primary": query = query.where(TaskRecord.result_type == "error") if day_7_click == "primary": day_7_ago = datetime.datetime.now() - datetime.timedelta(days=7) query = query.where(TaskRecord.run_date >= day_7_ago) if day_30_click == "primary": day_30_ago = datetime.datetime.now() - datetime.timedelta(days=30) query = query.where(TaskRecord.run_date >= day_30_ago) if dt_range: query = query.where(TaskRecord.run_date <= dt_range[1]).where(TaskRecord.run_date >= dt_range[0]) tabel_data = [{ "id": i.id, "run_date": str(i.run_date)[:19], "result_type": {'status': i.result_type, 'text': i.result_type}, 'detail_log': {'content': "查看", 'type': 'default', "color": "blue"}, } for i in query.paginate(pagination['current'], pagination['pageSize'])] pagination = { 'total': len(query), 'current': 1, 'pageSize': 10, 'showSizeChanger': True, 'pageSizeOptions': [10, 20, 30], 'position': 'bottomCenter', 'showQuickJumper': True, } return tabel_data, pagination return dash.no_update, dash.no_update # =========================== 渲染 觸發記錄列表 =========================== @self.app.callback( Input(f'task-table', 'recentlyButtonClickedRow')) def res_list_show(row_data): query = TaskRecord.select().where(TaskRecord.task_id == row_data["id"]) dash.set_props("res-table", {"data": [{ "id": i.id, "run_date": str(i.run_date)[:19], "result_type": {'status': i.result_type, 'text': i.result_type}, 'detail_log': {'content': "查看", 'type': 'default', "color": "blue"}, } for i in query.paginate(1, 10)]}) dash.set_props("res-table", {"pagination": { 'total': len(query), 'current': 1, 'pageSize': 10, 'showSizeChanger': True, 'pageSizeOptions': [10, 20, 30], 'position': 'bottomCenter', 'showQuickJumper': True, }}) # =========================== 渲染 觸發記錄詳情 =========================== @self.app.callback( Input(f'res-table', 'recentlyButtonClickedDataIndex'), Input(f'res-table', 'recentlyButtonClickedRow')) def res_detail_show(_, row_data): if _: obj = TaskRecord.get_or_none(row_data["id"]) dash.set_props( "log-detail-drawer", { "children": [ fac.AntdCollapse( fac.AntdInput(value=obj.command, mode="text-area", style=style(border=0)), title='執行信息', style=style(marginBottom=10) ), fac.AntdCollapse( fac.AntdInput(value=obj.output, mode="text-area", style=style(border=0)), title='標準輸出', style=style(marginBottom=10) ), fac.AntdCollapse( fac.AntdInput(value=obj.error, mode="text-area", style=style(border=0)), title='異常輸出', ), ] } ) dash.set_props("log-detail-drawer", {"visible": True}) dash.set_props("res-table", {"recentlyButtonClickedDataIndex": None}) # =========================== 點擊 "編輯"/"查看" 彈出抽屜, 更新內容 =========================== @self.app.callback( Input(f'task-table', 'recentlyButtonClickedDataIndex'), Input(f'task-table', 'recentlyButtonClickedRow'), prevent_initial_call=True) def open_edit_drawer(c_index, row_data): # 編輯 if c_index == "edit_row": # 渲染展示文本 dash.set_props("task-form-button", {"children": "編輯保存"}) dash.set_props("task-drawer", {"title": "編輯任務"}) dash.set_props("task-table", {"recentlyButtonClickedDataIndex": ""}) # 更新表單value dash.set_props("task-name", {"value": row_data["task_name"]}) dash.set_props("task-type", {"value": row_data["task_type"]}) dash.set_props("command", {"value": row_data["command"]}) schedule_type = row_data["schedule_type"] schedule_config = row_data["schedule_config"] if schedule_type == "周期調用": dash.set_props("config-input-cycle", {"value": int(schedule_config)}) elif schedule_type == "日期調用(單次)": dash.set_props("config-input-date", {"value": datetime.datetime.strptime(schedule_config, "%Y-%m-%d %H:%M:%S")}) elif schedule_type == 'Cron 調用': dash.set_props("config-input-corn", {"value": int(schedule_config)}) dash.set_props("schedule-type", {"value": schedule_type}) # 打開抽屜 dash.set_props("task-drawer", {"visible": True}) # 觸發記錄 if c_index == "log_row": dash.set_props("log-drawer", {"visible": True}) dash.set_props("task-table", {"recentlyButtonClickedDataIndex": ""}) dash.set_props("res-table", {"pagination": { 'total': len(TaskRecord.select().where(TaskRecord.task_id == row_data["id"])), 'current': 1, 'pageSize': 10, 'showSizeChanger': True, 'pageSizeOptions': [10, 20, 30], 'position': 'bottomCenter', 'showQuickJumper': True, }}) # =========================== 切換 "調度類型",調整輸入框展示信息 =========================== @self.app.callback(Input('schedule-type', 'value')) def schedule_switch(schedule_type): dash.set_props("config-input-cycle", {"style": style(display="none")}) dash.set_props("config-input-date", {"style": style(display="none")}) dash.set_props("config-input-corn", {"style": style(display="none")}) if schedule_type == '周期調用': dash.set_props("schedule-config-form-item", {"label": "周期(秒)"}) dash.set_props("config-input-cycle", {"style": None}) elif schedule_type == '日期調用(單次)': dash.set_props("schedule-config-form-item", {"label": "運行日期"}) dash.set_props("config-input-date", {"style": None}) elif schedule_type == 'Cron 調用': dash.set_props("schedule-config-form-item", {"label": "Cron 表達式"}) dash.set_props("config-input-corn", {"style": None}) # =========================== 切換 "任務類型", 調整輸入框展示信息 =========================== @self.app.callback(Input('task-type', 'value')) def type_switch(task_type): if task_type == 'Python 腳本': dash.set_props("command-form-item", {"label": "python 腳本"}) elif task_type == 'Shell 腳本': dash.set_props("command-form-item", {"label": "shell 腳本"}) elif task_type == 'API 調用': dash.set_props("command-form-item", {"label": "URL"}) # =========================== 點擊 "創建保存", 提交表單, 創建任務 =========================== @self.app.callback( Input('task-form-button', 'nClicks'), State('task-name', 'value'), State('task-type', 'value'), State('command', 'value'), State('schedule-type', 'value'), State('config-input-cycle', 'value'), State('config-input-date', 'value'), State('config-input-corn', 'value')) def add_task(_, task_name, task_type, command, schedule_type, cycle, date, corn): schedule_config = "" if schedule_type == "周期調用": schedule_config = cycle elif schedule_type == "日期調用(單次)": schedule_config = str(date)[:19] elif schedule_type == 'Cron 調用': schedule_config = corn Task.create( task_name=task_name, task_type=task_type, command=command, schedule_type=schedule_type, schedule_config=schedule_config, ) # 創建任務 # self.create_task(task) # 創建后, 默認不激活 dash.set_props("task-table", {"data": self.make_tabel_data(Task.select())}) dash.set_props("task-drawer", {"visible": False}) # =========================== 任務狀態修改 =========================== @self.app.callback( Input(f'task-table', 'recentlySwitchDataIndex'), Input(f'task-table', 'recentlySwitchStatus'), Input(f'task-table', 'recentlySwitchRow'), prevent_initial_call=True) def task_table_row_change(sw_k, sw_v, row_data): task_id = row_data["id"] task = Task.get_by_id(task_id) if sw_k == "is_active": # 更新記錄 task.is_active = sw_v task.save() # 停止或重新啟動任務 if task.is_active: self.scheduler.create(task) else: self.scheduler.destroy(task) # =========================== 表內刪除 操作 =========================== @self.app.callback( Output(f'task-table', 'data', allow_duplicate=True), Output(f'task-table', 'pagination', allow_duplicate=True), Input(f'task-table', 'recentlyButtonClickedDataIndex'), Input(f'task-table', 'recentlyButtonClickedRow'), State(f'task-table', 'pagination'), prevent_initial_call=True) def task_del(c_index, row_data, pagination): task_id = row_data.get('id') # 獲取項目ID' if c_index == "del_row": # 若啟動則刪除任務 if row_data["is_active"]["checked"]: self.scheduler.destroy(task_id=task_id) # 刪除任務對象 Task.delete().where(Task.id == task_id).execute() # 重新獲取數據 query = Task.select() if pagination: query = query.paginate(pagination['current'], pagination['pageSize']) return self.make_tabel_data(query), {**pagination, 'total': len(query)} if pagination else dash.no_update return dash.no_update, dash.no_update def start(self): self.app.run_server(debug=True) # 啟動應用 if __name__ == '__main__': td = TaskHandler() # td.reset_db() # 如有數據庫重建需求 td.start()

      安裝依賴后 解除  td.reset_db() 行進行初始化數據庫創建

      數據庫創建完成后, 再次啟動服務, 則直接訪問 127.0.0.1:5080 即可進行所有功能

      相關說明

      1. 僅支持了本地化執行, 不支持遠程作業調度 

      2. 若想實現遠程調度則需要在開發新頁面和建模表實現ssh訪問憑證的curd

      3. 定時任務基于 apscheduler, 相關的詳細使用文檔可參考  這個博客里面寫的很詳細

      4. 相當于實現了一個前后端不分離的實現作業調度雖小但全的麻雀demo, 簡單便捷, 堪堪可用

      5. 還是要吐槽一句前端寫起來屬實蛋疼,,,,,, 真特么是每個交互順序都要一點一點扣出來,,,,麻

       

      posted @ 2025-02-20 17:00  羊駝之歌  閱讀(287)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 成年入口无限观看免费完整大片| 欧美日本一区二区视频在线观看| 精品午夜福利在线视在亚洲| 国产99青青成人A在线| 国产va免费精品观看| 潮喷失禁大喷水无码| 视频免费完整版在线播放| 成人看的污污超级黄网站免费| 天堂网在线观看| 国产91午夜福利精品| 四虎成人精品永久免费av| 99久久精品国产一区二区蜜芽| 90后极品粉嫩小泬20p| 成人亚洲av免费在线| 国产高清av首播原创麻豆| 亚洲精品韩国一区二区| 成全我在线观看免费第二季| 少妇人妻偷人免费观看| 性欧美乱熟妇xxxx白浆| 国产精品免费中文字幕| 国产成人无码www免费视频播放| 中文字幕永久精品国产| 亚洲日韩国产精品第一页一区| 亚洲理论在线A中文字幕| av无码精品一区二区三区宅噜噜| 国产一精品一av一免费| 自拍视频亚洲精品在线| 国产精品久久久久不卡绿巨人| 顺昌县| 国产蜜臀视频一区二区三区| 免费看亚洲一区二区三区| 91国在线啪精品一区| 亚洲avav天堂av在线网爱情| 91精品91久久久久久| 国产线播放免费人成视频播放| 国内精品久久久久精免费| 日韩精品三区二区三区| 亚洲精品日韩在线丰满| 又污又爽又黄的网站| 亚洲精品一二三四区| 与子乱对白在线播放单亲国产|