Переглянути джерело

425更新持久化登陆、完善进程管理器

dev 2 тижнів тому
батько
коміт
1eb83837b7
58 змінених файлів з 250 додано та 245 видалено
  1. BIN
      backend/api/__pycache__/__init__.cpython-310.pyc
  2. BIN
      backend/api/__pycache__/admin.cpython-310.pyc
  3. BIN
      backend/api/__pycache__/api_calculate.cpython-310.pyc
  4. BIN
      backend/api/__pycache__/api_graph.cpython-310.pyc
  5. BIN
      backend/api/__pycache__/api_prepare.cpython-310.pyc
  6. BIN
      backend/api/__pycache__/api_rawDataTrans.cpython-310.pyc
  7. BIN
      backend/api/__pycache__/api_results.cpython-310.pyc
  8. BIN
      backend/api/__pycache__/api_user.cpython-310.pyc
  9. BIN
      backend/api/__pycache__/apps.cpython-310.pyc
  10. BIN
      backend/api/__pycache__/serializers.cpython-310.pyc
  11. BIN
      backend/api/__pycache__/tokenAuthentication.cpython-310.pyc
  12. BIN
      backend/api/__pycache__/urls.cpython-310.pyc
  13. BIN
      backend/api/__pycache__/utils.cpython-310.pyc
  14. 5 0
      backend/api/api_rawDataTrans.py
  15. BIN
      backend/api/migrations/__pycache__/0001_initial.cpython-310.pyc
  16. BIN
      backend/api/migrations/__pycache__/0002_alter_user_options_user_last_login.cpython-310.pyc
  17. BIN
      backend/api/migrations/__pycache__/0003_view_file.cpython-310.pyc
  18. BIN
      backend/api/migrations/__pycache__/0004_rename_display_name_user_displayname_file_usage.cpython-310.pyc
  19. BIN
      backend/api/migrations/__pycache__/0005_file_associate_file_content.cpython-310.pyc
  20. BIN
      backend/api/migrations/__pycache__/0006_alter_file_associate.cpython-310.pyc
  21. BIN
      backend/api/migrations/__pycache__/0007_fileinfo.cpython-310.pyc
  22. BIN
      backend/api/migrations/__pycache__/0008_mission_result.cpython-310.pyc
  23. BIN
      backend/api/migrations/__pycache__/0009_alter_fileinfo_file_alter_mission_name.cpython-310.pyc
  24. BIN
      backend/api/migrations/__pycache__/0010_algorithm_plan.cpython-310.pyc
  25. BIN
      backend/api/migrations/__pycache__/0011_result_plan_result_state.cpython-310.pyc
  26. BIN
      backend/api/migrations/__pycache__/0012_result_edgefile_result_nodefile_alter_result_plan.cpython-310.pyc
  27. BIN
      backend/api/migrations/__pycache__/0013_remove_result_state_alter_file_usage.cpython-310.pyc
  28. BIN
      backend/api/migrations/__pycache__/0014_result_progress.cpython-310.pyc
  29. BIN
      backend/api/migrations/__pycache__/0015_mission_state.cpython-310.pyc
  30. BIN
      backend/api/migrations/__pycache__/0016_alter_result_edgefile_alter_result_nodefile.cpython-310.pyc
  31. BIN
      backend/api/migrations/__pycache__/0017_graph_graphtoken.cpython-310.pyc
  32. BIN
      backend/api/migrations/__pycache__/0018_rename_edgemap_graph_edges_and_more.cpython-310.pyc
  33. BIN
      backend/api/migrations/__pycache__/0019_alter_graph_user.cpython-310.pyc
  34. BIN
      backend/api/migrations/__pycache__/__init__.cpython-310.pyc
  35. BIN
      backend/api/models/__pycache__/__init__.cpython-310.pyc
  36. BIN
      backend/api/models/__pycache__/algorithm.cpython-310.pyc
  37. BIN
      backend/api/models/__pycache__/file.cpython-310.pyc
  38. BIN
      backend/api/models/__pycache__/graph.cpython-310.pyc
  39. BIN
      backend/api/models/__pycache__/mission.cpython-310.pyc
  40. BIN
      backend/api/models/__pycache__/plan.cpython-310.pyc
  41. BIN
      backend/api/models/__pycache__/result.cpython-310.pyc
  42. BIN
      backend/api/models/__pycache__/user.cpython-310.pyc
  43. BIN
      backend/api/models/__pycache__/view.cpython-310.pyc
  44. BIN
      backend/backend/__pycache__/__init__.cpython-310.pyc
  45. BIN
      backend/backend/__pycache__/settings.cpython-310.pyc
  46. BIN
      backend/backend/__pycache__/urls.cpython-310.pyc
  47. BIN
      backend/backend/__pycache__/wsgi.cpython-310.pyc
  48. BIN
      backend/db.sqlite3
  49. BIN
      scheduler/__pycache__/processManager.cpython-310.pyc
  50. BIN
      scheduler/__pycache__/utils.cpython-310.pyc
  51. 56 44
      scheduler/processManager.py
  52. 25 59
      scheduler/scheduler.py
  53. 28 47
      scheduler/utils.py
  54. 1 0
      viewer/package.json
  55. 97 88
      viewer/pnpm-lock.yaml
  56. 7 2
      viewer/src/api/axios.js
  57. 4 0
      viewer/src/store/userInfo.js
  58. 27 5
      viewer/src/views/dashoard/calculate.vue

BIN
backend/api/__pycache__/__init__.cpython-310.pyc


BIN
backend/api/__pycache__/admin.cpython-310.pyc


BIN
backend/api/__pycache__/api_calculate.cpython-310.pyc


BIN
backend/api/__pycache__/api_graph.cpython-310.pyc


BIN
backend/api/__pycache__/api_prepare.cpython-310.pyc


BIN
backend/api/__pycache__/api_rawDataTrans.cpython-310.pyc


BIN
backend/api/__pycache__/api_results.cpython-310.pyc


BIN
backend/api/__pycache__/api_user.cpython-310.pyc


BIN
backend/api/__pycache__/apps.cpython-310.pyc


BIN
backend/api/__pycache__/serializers.cpython-310.pyc


BIN
backend/api/__pycache__/tokenAuthentication.cpython-310.pyc


BIN
backend/api/__pycache__/urls.cpython-310.pyc


BIN
backend/api/__pycache__/utils.cpython-310.pyc


+ 5 - 0
backend/api/api_rawDataTrans.py

@@ -15,6 +15,9 @@ import requests
 
 import json, csv
 
+PLAN_FAILED = -1
+MISSION_FAILED = -2
+
 class RawDataTrans(APIView):
     authentication_classes = []
     permission_classes = []
@@ -42,6 +45,7 @@ class RawDataTrans(APIView):
             if param is None:
                 print("结果传递参数不足")
                 return failed(message="缺少结果参数")
+            
         if int(progress) == 100:
             if not nodes or not edges:
                 print("进度完成却没有返回结果数据")
@@ -74,6 +78,7 @@ class RawDataTrans(APIView):
 
             else:
                 # 进度不到百分百,正在执行中,仅更新进度数值
+                # 注意使用负数进度值表示单个处理失败或整个任务失败
                 result.progress = int(progress)
                 result.save()
             return success(message="保存结果文件成功")

BIN
backend/api/migrations/__pycache__/0001_initial.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0002_alter_user_options_user_last_login.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0003_view_file.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0004_rename_display_name_user_displayname_file_usage.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0005_file_associate_file_content.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0006_alter_file_associate.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0007_fileinfo.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0008_mission_result.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0009_alter_fileinfo_file_alter_mission_name.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0010_algorithm_plan.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0011_result_plan_result_state.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0012_result_edgefile_result_nodefile_alter_result_plan.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0013_remove_result_state_alter_file_usage.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0014_result_progress.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0015_mission_state.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0016_alter_result_edgefile_alter_result_nodefile.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0017_graph_graphtoken.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0018_rename_edgemap_graph_edges_and_more.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0019_alter_graph_user.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/__init__.cpython-310.pyc


BIN
backend/api/models/__pycache__/__init__.cpython-310.pyc


BIN
backend/api/models/__pycache__/algorithm.cpython-310.pyc


BIN
backend/api/models/__pycache__/file.cpython-310.pyc


BIN
backend/api/models/__pycache__/graph.cpython-310.pyc


BIN
backend/api/models/__pycache__/mission.cpython-310.pyc


BIN
backend/api/models/__pycache__/plan.cpython-310.pyc


BIN
backend/api/models/__pycache__/result.cpython-310.pyc


BIN
backend/api/models/__pycache__/user.cpython-310.pyc


BIN
backend/api/models/__pycache__/view.cpython-310.pyc


BIN
backend/backend/__pycache__/__init__.cpython-310.pyc


BIN
backend/backend/__pycache__/settings.cpython-310.pyc


BIN
backend/backend/__pycache__/urls.cpython-310.pyc


BIN
backend/backend/__pycache__/wsgi.cpython-310.pyc


BIN
backend/db.sqlite3


BIN
scheduler/__pycache__/processManager.cpython-310.pyc


BIN
scheduler/__pycache__/utils.cpython-310.pyc


+ 56 - 44
scheduler/processManager.py

@@ -9,6 +9,8 @@ import json
 from queue import Queue, Empty
 from typing import Dict, Optional, List, Union
 import requests
+from utils import store
+
 
 from utils import SCHEDULER_BASE_URL, BACKEND_BASE_URL
 
@@ -197,47 +199,47 @@ class ProcessManager:
                 try:
                     proc = psutil.Process(pid)
                     proc.terminate()
-                    del self.processes[pid]
-                    # 清理读取线程
-                    if pid in self._reader_threads:
-                        for t in self._reader_threads[pid]:
-                            t.join()
-                        del self._reader_threads[pid]
-                    self.logger.info(f"移除处理进程监视 MissionId: {missionId} PlanId: {planId}")
-                    return True
                 except psutil.NoSuchProcess:
                     self.logger.error(f"进程已自行退出 MissionId: {missionId} PlanId: {planId}")
-                    return True
                 except Exception as error:
                     self.logger.error(f"移除处理进程监视失败 MissionId: {missionId} PlanId: {planId} Error: {error}")
                     return False
-        self.logger.info(f"该处理进程不在监视中 MissionId: {missionId} PlanId: {planId}")
+                # 清理进程列表
+                del self.processes[pid]
+                # 清理读取线程
+                if pid in self._reader_threads:
+                    for t in self._reader_threads[pid]:
+                        t.join()
+                    del self._reader_threads[pid]
+                self.logger.info(f"移除处理进程监视 MissionId: {missionId} PlanId: {planId}")
+                return True
+        self.logger.info(f"该处理进程不在监视进程表中 MissionId: {missionId} PlanId: {planId}")
         return True
     
 
     def remove_process(self, pid_to_del: int):
-        self.logger.error(f"移除处理进程-with pid")
         try:
-            # 清理读取线程
-            if pid_to_del in self._reader_threads:
-                for t in self._reader_threads[pid_to_del]:
-                    t.join()
-                del self._reader_threads[pid_to_del]
+            # 清理监视进程列表
             for pid, info in self.processes.items():
                 if pid == pid_to_del:
                     missionId = info['meta']['mission']['id']
                     planId = info["meta"]["plan"]["id"]
                     proc = psutil.Process(pid)
                     proc.terminate()
-                    del self.processes[pid]
                     self.logger.info(f"移除处理进程监视成功 MissionId: {missionId} PlanId: {planId}")
-                    return
-            self.logger.error(f"未找到请求移除的进程 MissionId: {missionId} PlanId: {planId}")
         except psutil.NoSuchProcess:
             self.logger.error(f"进程已自行退出 MissionId: {missionId} PlanId: {planId}")
         except Exception as error:
             self.logger.error(f"移除处理进程监视-with pid失败 pid:{pid_to_del}")
-
+            return False
+        # 移除监视进程列表
+        del self.processes[pid]
+        # 清理读取线程
+        if pid_to_del in self._reader_threads:
+            for t in self._reader_threads[pid_to_del]:
+                t.join()
+            del self._reader_threads[pid_to_del]
+        return True
 
 
     def _monitor_loop(self):
@@ -282,17 +284,12 @@ class ProcessManager:
                     response = {}
                     try:
                         while True:
-                            self.logger.info(f"111")
                             pipe_type, message = info["msg_queue"].get_nowait()
-                            self.logger.info(f"222")
                             response = self._handle_process_message(pid, pipe_type, message)
-                            self.logger.info(f"返回信息1{response}")
                     except Empty:
-                        self.logger.info(f"333")
                         pass
                     except Exception as error:
                         self.logger.error(f"ERROR:{error}")
-                    self.logger.info(f"返回信息2{response}")
                     if 'finished' in response and response['finished']:
                         # 虽然已经找不到进程,但是由于进程已正常退出
                         # 将获取的result缓存,待释放锁后向调度器汇报
@@ -320,8 +317,8 @@ class ProcessManager:
                             # 正常退出
                             # 将获取的result缓存,待释放锁后向调度器汇报
                             reportDatas.append({
-                                'missionId': info["meta"]["mission"]["id"],
-                                'planId': info["meta"]["plan"]["id"],
+                                'missionId': int(info["meta"]["mission"]["id"]),
+                                'planId': int(info["meta"]["plan"]["id"]),
                                 'state': 'DONE',
                                 'results': response['results'],
                             })
@@ -340,11 +337,27 @@ class ProcessManager:
                         t.join()
                     del self._reader_threads[pid]    
         # 锁已释放
-        # 依次汇报已结束任务,获取下一步任务
+        # 依次提交已完成任务,获取下一步任务
+        # 注意在收到results通信时,就已经将结果发送至django,这里不需要处理
         for report in reportDatas:
-            response = requests.post(SCHEDULER_BASE_URL + "/report", json=report)
-            self.logger.info(f"进程结果已提交调度器 mission:{report['missionId']} plan:{report['planId']} response:{response}")
-
+            # 向store提交
+            missionId = report['missionId']
+            planId=report['planId']
+            results=report['results']
+            for nextTask in store.solveMission(missionId=missionId, planId=planId, results=results):
+                task = store.prepareTask(missionId=missionId, planId=nextTask['id'])
+                if self.spawn(command=task['command'], cwd=task['cwd'], plan=task['plan'], mission=task['mission']):
+                    self.logger.info(f"创建后续计算任务成功 MissionId:{missionId} PlanId:{task['plan']['id']}")
+                else:
+                    self.logger.error(f"创建后续计算任务失败 MissionId:{missionId} PlanId:{task['plan']['id']}")
+                    # 任务无法继续,向django汇报下一个任务失败
+                    response = requests.post(BACKEND_BASE_URL + "/rawDataTrans/", json={
+                        'missionId': task['mission']['id'],
+                        'planId': task['plan']['id'],
+                        'progress': -1, # -1 表示单个任务失败 -2 表示全体mission失败
+                    })
+                    
+                    
     def _handle_process_message(self, pid:int, pipe_type:str, message:str):
         """处理来自子进程的通信消息"""
         try:
@@ -366,7 +379,6 @@ class ProcessManager:
                 # 获得返回结果,向django汇报
                 response = requests.post(BACKEND_BASE_URL + "/rawDataTrans/", json=data.get("data"))
                 self.logger.info(f"进程结果已向后端服务器反馈 pid:{pid} response:{response}")
-
                 self.logger.info(f"进程 {pid} 报告已完成")
                 # 标记该进程正常退出
                 return {'finished': True, 'results': data.get('data')}
@@ -380,25 +392,25 @@ class ProcessManager:
     def _handle_crash(self, pid: int, info: dict):
         """进程崩溃处理逻辑"""
         # 读取最后10行错误日志
-        try:
-            with open(info["log_stderr"]) as f:
-                last_lines = "".join(f.readlines()[-10:])
-        except Exception:
-            last_lines = "无法读取日志"
-
+        # try:
+        #     with open(info["log_stderr"]) as f:
+        #         last_lines = "".join(f.readlines()[-10:])
+        # except Exception:
+        #     last_lines = "无法读取日志"
         self.logger.error(
             f"进程崩溃 PID={pid}\n"
             f"命令: {info['command']}\n"
-            f"最后错误输出:\n{last_lines}"
         )
         # 发现进程崩溃,向django汇报任务失败
-        response = requests.post(BACKEND_BASE_URL + "/rawDataTrans/", json={'missionId': info['meta']['mission']['id'], 'planId': info['meta']['plan']['id'], 'progress': -1})
-        # 向flask发送进程崩溃信息
-        response = requests.post(SCHEDULER_BASE_URL + '/report', json={'missionId': info['meta']['mission']['id'], 'planId': info['meta']['plan']['id'], 'state': 'DEAD', 'results': None})
+        response = requests.post(BACKEND_BASE_URL + "/rawDataTrans/", json={'missionId': info['meta']['mission']['id'], 'planId': info['meta']['plan']['id'], 'progress': -1}) # -1表示单个任务失败
+        # 调用store终止该mission
+        store.removeMission(missionId=info['meta']['mission']['id'])
+        self.logger.error(f"任务进程发生崩溃,Mission {info['meta']['mission']['id']}已终止")
 
     def _handle_timeout(self, pid: int, info: dict):
         """进程超时处理逻辑"""
         # 记录诊断信息
         self.logger.error(f"进程超时处理 PID={pid} | 运行时间: {time.time() - info['start_time']:.1f}s")
-        # 向flask发送进程超时信息
-        response = requests.post(SCHEDULER_BASE_URL + '/report', json={'missionId': info['meta']['mission']['id'], 'planId': info['meta']['plan']['id'], 'state': 'TIMEOUT', 'results': None})
+        # 调用store终止该mission
+        store.removeMission(missionId=info['meta']['mission']['id'])
+        self.logger.error(f"任务进程发生超时,Mission {info['meta']['mission']['id']}已终止")

+ 25 - 59
scheduler/scheduler.py

@@ -5,7 +5,7 @@ import logging
 import csv
 from pathlib import Path
 import requests
-from utils import Store, TaskState
+from utils import store, TaskState
 from processManager import ProcessManager
 
 BASE_DIR = Path(__file__).resolve().parent
@@ -13,8 +13,6 @@ DB_DIR = BASE_DIR.parent / 'backend' / 'db.sqlite3'
 
 logger = logging.getLogger("Scheduler")
 
-store = Store()
-
 app = Flask(__name__)
 app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + str(DB_DIR) + '?check_same_thread=False'
 app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
@@ -49,66 +47,24 @@ def fetchData():
         logger.error(f"获取计算任务输入数据错误 MissionId:{missionId} PlanId:{planId}")
         return jsonify([])
 
-@app.route('/report', methods=['POST'])
-def report():
-    missionId = int(request.json['missionId'])
-    planId = int(request.json['planId'])
-    state = request.json['state']
-    results = request.json['results']
-    if state == "CRASH":
-        # 已无法找到进程,检查是否已经结束
-        if store.checkActiveTask(missionId=missionId, planId=planId, state=TaskState.DONE):
-            return jsonify({"code": "OK"})
-        else:
-            # logger.info(f"计算任务崩溃: MissionId:{mission['id']} PlanId:{plan['id']}")
-            return jsonify({"code": "ERROR"})
-    if state == "DONE":
-        if store.updateActiveTask(missionId=missionId, planId=planId, state=TaskState.DONE):
-            logger.info(f"计算任务完成: MissionId:{missionId} PlanId:{planId}")
-            # 现有任务完成,搜索下一任务
-            nextTasks = store.solveMission(missionId=missionId, planId=planId, results=results)
-            logger.info(f"Next Tasks{nextTasks}")
-            for nextTask in nextTasks:
-                task = store.prepareTask(missionId=missionId, planId=nextTask['id'])
-                if manager.spawn(command=task['command'], cwd=task['cwd'], plan=task['plan'], mission=task['mission']):
-                    store.addActiveTask(missionId=missionId, planId=task['plan']['id'])
-                    logger.info(f"创建后续计算任务成功 MissionId:{missionId} PlanId:{task['plan']['id']}")
-                else:
-                    logger.info(f"创建后续计算任务失败 MissionId:{missionId} PlanId:{task['plan']['id']}")
-            return jsonify({"code": "OK"})
-        else:
-            logger.error(f"计算任务无法完成: MissionId:{missionId} PlanId:{planId}")
-    # 超时任务处理逻辑待完善
-    if state == "DEAD" or state == "TIMEOUT":
-        if store.removeActiveTask(missionId=missionId, planId=planId):
-            logger.info(f"移除终止计算任务: MissionId:{missionId} PlanId:{planId}")
-            return jsonify({"code": "OK"})
-        else:
-            logger.error(f"移除错误计算任务失败,已强制清除: MissionId:{missionId} PlanId:{planId}")
-            return jsonify({"code": "ERROR"})
-    # 主控只负责构建任务队列,然后调用子进程,子进程运行结束后发送报告,查询队列,继续运行。同时向Django发起访问,将运行结果更新到数据库,前端轮询时即可获取结果
-    logger.error(f"无法识别的任务状态: MissionId:{missionId} PlanId:{planId} State:{state}")
-    return jsonify({"code": "ERROR"})
 
 @app.route('/addMission', methods=['POST'])
-def start_task():
+def add_mission():
     mission = request.json['mission']
     plans = request.json['plans']
     # 将mission加入任务列表
     if store.addMission(mission, plans):
         # 读取task列表,启动任务
         for task in store.initMissionTasks(missionId=mission['id']):
-            if manager.spawn(command=task['command'], cwd=task['cwd'], plan=task['plan'], mission=task['mission']):
-                store.addActiveTask(missionId=mission['id'], planId=task['plan']['id'])
-            else:
+            if not manager.spawn(command=task['command'], cwd=task['cwd'], plan=task['plan'], mission=task['mission']):
                 logger.error(f"创建计算任务失败 :MissionId:{mission['id']} PlanId:{task['plan']['id']}")
-
+                return jsonify({"code": "ERROR", "status": ""})
         return jsonify({"code": "OK", "status": "started"})
     else:
-        return jsonify({"code": "ERROR", "status": "duplicated"})
+        return jsonify({"code": "ERROR", "status": "Failed to create mission"})
 
 @app.route('/pauseMission', methods=['POST'])
-def pause_task():
+def pause_mission():
     mission = request.json['mission']
     # 暂停mission的方法是停止当前正在执行的算法子进程,但是保留mission任务栈,等待恢复命令
     # 在store中,mission的task保存了当前正在执行的所有plan,因此并不需要对store做特殊处理
@@ -119,22 +75,32 @@ def pause_task():
         return jsonify({"code": "ERROR", "status": "Failed to pause"})
     
 @app.route('/resumeMission', methods=['POST'])
-def resume_task():
+def resume_mission():
     mission = request.json['mission']
+    # store中的恢复mission即读取该mission现有的tasks列表,将其中的任务重新加入进程管理器中运行
     resumedTasks = store.resumeMission(missionId=int(mission['id']))
     for nextTask in resumedTasks:
         task = store.prepareTask(missionId=int(mission['id']), planId=nextTask['id'])
-        if manager.spawn(command=task['command'], cwd=task['cwd'], plan=task['plan'], mission=task['mission']):
-            logger.info(f"任务 MissionId:{mission['id']} 已恢复")
-        else:
-            logger.info(f"任务 MissionId:{mission['id']} 恢复运行出错")
-
-def stop_task():
+        if not manager.spawn(command=task['command'], cwd=task['cwd'], plan=task['plan'], mission=task['mission']):
+            logger.info(f"任务 MissionId:{mission['id']} 恢复运行出错,尝试将其移除")
+            # 任务恢复运行出错时,将其彻底停止以避免bug
+            if not manager.stop(missionId=int(mission['id'])):
+                logger.info(f"移除 MissionId:{mission['id']} 运行中进程出错")
+            if store.stopMission(missionId=int(mission['id'])):
+                logger.info(f"移除 MissionId:{mission['id']} 成功")
+            else:
+                logger.info(f"移除 MissionId:{mission['id']} 任务数据出错")
+    logger.info(f"任务 MissionId:{mission['id']} 已恢复")
+    
+    
+@app.route('/stopMission', methods=['POST'])
+def stop_mission():
     # 被停止的mission将无法恢复,因此可以直接删除其所有上下文消息
     mission = request.json['mission']
-    plan = manager.stop(missionId=int(mission['id']))
-    if not plan:
+    # 首先在进程管理器中停止运行的进程
+    if not manager.stop(missionId=int(mission['id'])):
         logger.info(f"停止 MissionId:{mission['id']} 出错,未能停止现有进程")
+    # 其次在数据管理器中删除该Mission的所有数据
     if store.stopMission(missionId=int(mission['id'])):
         logger.info(f"停止 MissionId:{mission['id']} 成功")
     else:

+ 28 - 47
scheduler/utils.py

@@ -28,45 +28,11 @@ for algo in algoConfig:
 
 class Store:
     missions = []
-    activeTasks = []
-
-    def addActiveTask(self, missionId: int, planId: int):
-        self.activeTasks.append({
-            'missionId': missionId,
-            'planId': planId,
-            'state': TaskState.INIT,
-        })
-    
-    def checkActiveTask(self, missionId: int, planId: int, state: TaskState):
-        for task in self.activeTasks:
-            if task['missionId'] == missionId and task['planId'] == planId:
-                if task['state'] == state:
-                    return True
-                break
-        return False
-
-    def updateActiveTask(self, missionId: int, planId: int, state: TaskState):
-
-        for task in self.activeTasks:
-            if task['missionId'] == missionId and task['planId'] == planId:
-                # 已经DONE的task不应该再被修改状态
-                if task['state'] != TaskState.DONE:
-                    task['state'] = state
-                    return True
-                break
-        return False
-
-    def removeActiveTask(self, missionId: int, planId: int):
-        logger.info(f"selfACTIVETASL{self.activeTasks}")
-        self.activeTasks = [task for task in self.activeTasks if task['missionId'] != missionId and task['planId'] != planId]
-        logger.info(f"selfACTIVETASL{self.activeTasks}")
-        return True
-
     # 添加任务时仅放入第一级待计算plans,后续每个plan完成计算后根据children列表寻找下一个plan继续计算
     def addMission(self, mission: dict, plans: list):
         # 判断是否存在重复mission
         if [m for m in self.missions if m['id'] == mission['id']]:
-            print("存在重复Mission,无法新建mission")
+            logger.error("存在重复Mission,无法新建mission")
             return False
 
         # 寻找初始并行任务, 默认第一个是根plan,后续所有都是它的子节点
@@ -90,11 +56,11 @@ class Store:
                     elif 'edges' in mission:
                         p['edges'] = mission['edges']
                     else:
-                        print("无法从初始任务中找到边信息")
+                        logger.error("无法从初始任务中找到边信息")
                 break
                     
         tasks = []
-        # 第一级plans
+        # 第一级plans,所有当前任务叫做tasks
         tasks.extend([p['id'] for p in originPlans])
 
         self.missions.append({
@@ -104,6 +70,18 @@ class Store:
         })
         return True
     
+    # 当出现进程错误或需要移除mission时使用
+    def removeMission(self, missionId: int):
+        mission = [m for m in self.missions if m['id'] == missionId]
+        if not mission:
+            logger.error("移除Mission时出错,未找到该Mission")
+            return
+        else:
+            mission = mission[0]
+        index = self.missions.index(mission)
+        del self.missions[index]
+        logger.info(f"Mission: {missionId} 已移除")
+        
     # mission中的某项任务完成计算
     def solveMission(self, missionId: int, planId: int, results: dict):
         mission = [m for m in self.missions if m['id'] == missionId]
@@ -139,7 +117,7 @@ class Store:
             mission['plans'] = [p for p in mission['plans'] if p['id'] != planId]
 
         else:
-            print("给出的不是task列表中计算任务?")
+            logger.error("给出的Mission中找不到完成的任务信息")
             return []
         # 返回下一轮需要调用的任务,如果存在并行,则列表长度大于1
         return [plan for plan in mission['plans'] if plan['id'] in children]
@@ -147,7 +125,7 @@ class Store:
     def resumeMission(self, missionId: int):
         mission = [m for m in self.missions if m['id'] == missionId]
         if not mission:
-            logger.info("未找到要恢复的mission")
+            logger.error("未找到要恢复的mission")
         else:
             mission = mission[0]
         # 返回所有的tasks,由于暂停时仅在maanger中停止了进程,而没有在store中处理,因此tasks中保存的就是此前的运行任务
@@ -156,7 +134,7 @@ class Store:
     def stopMission(self, missionId: int):
         mission = [m for m in self.missions if m['id'] == missionId]
         if not mission:
-            logger.info("未找到要停止的mission")
+            logger.error("未找到要停止的mission")
             return False
         # 停止后不需恢复,所以直接摘除该mission即可
         self.missions = [m for m in self.missions if m['id'] != missionId]
@@ -165,7 +143,7 @@ class Store:
     def initMissionTasks(self, missionId: int):
         mission = [m for m in self.missions if m['id'] == missionId]
         if not mission:
-            logger.info("未找到mission,是否错误传入更新请求")
+            logger.error("未找到mission,是否错误传入更新请求")
             return []
         else:
             mission = mission[0]
@@ -173,21 +151,21 @@ class Store:
         for plan in [plan for plan in mission['plans'] if plan['id'] in mission['tasks']]:
             algorithm = [algo for algo in algoList if algo['name'] == plan['algorithm']]
             if not algorithm:
-                print("未找到选择的算法,是否需要更新算法列表?")
+                logger.error("未找到选择的算法,是否需要更新算法列表?")
                 return []
             else:
                 algorithm = algorithm[0]
             preparedTasks.append({
+                'mission': {
+                    'id': mission['id'],
+                },
                 'plan': {
                     'id': plan['id'],
                 },
                 'cwd': str(algorithm['path']),
                 'command': algorithm['command'],
-                'mission': {
-                    'id': mission['id'],
-                },
             })
-            print("initMissionTasks is :", preparedTasks)
+            logger.info(f"初始化计算任务:{preparedTasks}")
         return preparedTasks
     
     def prepareTask(self, missionId: int, planId: int):
@@ -235,4 +213,7 @@ class Store:
                     'edges': plan['edges'],
                 }
         print("在plans中找不到对应的plan")
-        return {}
+        return {}
+
+# 被导出的全局store变量
+store = Store()

+ 1 - 0
viewer/package.json

@@ -19,6 +19,7 @@
     "element-plus": "^2.9.3",
     "file-saver": "^2.0.5",
     "html2canvas": "^1.4.1",
+    "js-cookie": "^3.0.5",
     "sass-embedded": "^1.83.4",
     "three": "^0.175.0",
     "troisjs": "^0.3.4",

+ 97 - 88
viewer/pnpm-lock.yaml

@@ -26,6 +26,9 @@ importers:
       html2canvas:
         specifier: ^1.4.1
         version: 1.4.1
+      js-cookie:
+        specifier: ^3.0.5
+        version: 3.0.5
       sass-embedded:
         specifier: ^1.83.4
         version: 1.83.4
@@ -234,163 +237,163 @@ packages:
     engines: {node: '>=6.9.0'}
 
   '@bufbuild/protobuf@2.2.3':
-    resolution: {integrity: sha512-tFQoXHJdkEOSwj5tRIZSPNUuXK3RaR7T1nUrPgbYX1pUbvqqaaZAsfo+NXBPsz5rZMSKVFrgK1WL8Q/MSLvprg==, tarball: https://registry.npmmirror.com/@bufbuild/protobuf/-/protobuf-2.2.3.tgz}
+    resolution: {integrity: sha512-tFQoXHJdkEOSwj5tRIZSPNUuXK3RaR7T1nUrPgbYX1pUbvqqaaZAsfo+NXBPsz5rZMSKVFrgK1WL8Q/MSLvprg==}
 
   '@ctrl/tinycolor@3.6.1':
     resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
     engines: {node: '>=10'}
 
   '@element-plus/icons-vue@2.3.1':
-    resolution: {integrity: sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==, tarball: https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz}
+    resolution: {integrity: sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==}
     peerDependencies:
       vue: ^3.2.0
 
   '@esbuild/aix-ppc64@0.24.2':
-    resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==, tarball: https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz}
+    resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==}
     engines: {node: '>=18'}
     cpu: [ppc64]
     os: [aix]
 
   '@esbuild/android-arm64@0.24.2':
-    resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==, tarball: https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz}
+    resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==}
     engines: {node: '>=18'}
     cpu: [arm64]
     os: [android]
 
   '@esbuild/android-arm@0.24.2':
-    resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==, tarball: https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.24.2.tgz}
+    resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==}
     engines: {node: '>=18'}
     cpu: [arm]
     os: [android]
 
   '@esbuild/android-x64@0.24.2':
-    resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==, tarball: https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.24.2.tgz}
+    resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==}
     engines: {node: '>=18'}
     cpu: [x64]
     os: [android]
 
   '@esbuild/darwin-arm64@0.24.2':
-    resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==, tarball: https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz}
+    resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==}
     engines: {node: '>=18'}
     cpu: [arm64]
     os: [darwin]
 
   '@esbuild/darwin-x64@0.24.2':
-    resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==, tarball: https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz}
+    resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==}
     engines: {node: '>=18'}
     cpu: [x64]
     os: [darwin]
 
   '@esbuild/freebsd-arm64@0.24.2':
-    resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==, tarball: https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz}
+    resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==}
     engines: {node: '>=18'}
     cpu: [arm64]
     os: [freebsd]
 
   '@esbuild/freebsd-x64@0.24.2':
-    resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==, tarball: https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz}
+    resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==}
     engines: {node: '>=18'}
     cpu: [x64]
     os: [freebsd]
 
   '@esbuild/linux-arm64@0.24.2':
-    resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==, tarball: https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz}
+    resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==}
     engines: {node: '>=18'}
     cpu: [arm64]
     os: [linux]
 
   '@esbuild/linux-arm@0.24.2':
-    resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==, tarball: https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz}
+    resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==}
     engines: {node: '>=18'}
     cpu: [arm]
     os: [linux]
 
   '@esbuild/linux-ia32@0.24.2':
-    resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==, tarball: https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz}
+    resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==}
     engines: {node: '>=18'}
     cpu: [ia32]
     os: [linux]
 
   '@esbuild/linux-loong64@0.24.2':
-    resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==, tarball: https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz}
+    resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==}
     engines: {node: '>=18'}
     cpu: [loong64]
     os: [linux]
 
   '@esbuild/linux-mips64el@0.24.2':
-    resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==, tarball: https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz}
+    resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==}
     engines: {node: '>=18'}
     cpu: [mips64el]
     os: [linux]
 
   '@esbuild/linux-ppc64@0.24.2':
-    resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==, tarball: https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz}
+    resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==}
     engines: {node: '>=18'}
     cpu: [ppc64]
     os: [linux]
 
   '@esbuild/linux-riscv64@0.24.2':
-    resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==, tarball: https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz}
+    resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==}
     engines: {node: '>=18'}
     cpu: [riscv64]
     os: [linux]
 
   '@esbuild/linux-s390x@0.24.2':
-    resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==, tarball: https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz}
+    resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==}
     engines: {node: '>=18'}
     cpu: [s390x]
     os: [linux]
 
   '@esbuild/linux-x64@0.24.2':
-    resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==, tarball: https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz}
+    resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==}
     engines: {node: '>=18'}
     cpu: [x64]
     os: [linux]
 
   '@esbuild/netbsd-arm64@0.24.2':
-    resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==, tarball: https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz}
+    resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==}
     engines: {node: '>=18'}
     cpu: [arm64]
     os: [netbsd]
 
   '@esbuild/netbsd-x64@0.24.2':
-    resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==, tarball: https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz}
+    resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==}
     engines: {node: '>=18'}
     cpu: [x64]
     os: [netbsd]
 
   '@esbuild/openbsd-arm64@0.24.2':
-    resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==, tarball: https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz}
+    resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==}
     engines: {node: '>=18'}
     cpu: [arm64]
     os: [openbsd]
 
   '@esbuild/openbsd-x64@0.24.2':
-    resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==, tarball: https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz}
+    resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==}
     engines: {node: '>=18'}
     cpu: [x64]
     os: [openbsd]
 
   '@esbuild/sunos-x64@0.24.2':
-    resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==, tarball: https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz}
+    resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==}
     engines: {node: '>=18'}
     cpu: [x64]
     os: [sunos]
 
   '@esbuild/win32-arm64@0.24.2':
-    resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==, tarball: https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz}
+    resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==}
     engines: {node: '>=18'}
     cpu: [arm64]
     os: [win32]
 
   '@esbuild/win32-ia32@0.24.2':
-    resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==, tarball: https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz}
+    resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==}
     engines: {node: '>=18'}
     cpu: [ia32]
     os: [win32]
 
   '@esbuild/win32-x64@0.24.2':
-    resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==, tarball: https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz}
+    resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==}
     engines: {node: '>=18'}
     cpu: [x64]
     os: [win32]
@@ -505,107 +508,107 @@ packages:
         optional: true
 
   '@rollup/rollup-android-arm-eabi@4.34.2':
-    resolution: {integrity: sha512-6Fyg9yQbwJR+ykVdT9sid1oc2ewejS6h4wzQltmJfSW53N60G/ah9pngXGANdy9/aaE/TcUFpWosdm7JXS1WTQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.2.tgz}
+    resolution: {integrity: sha512-6Fyg9yQbwJR+ykVdT9sid1oc2ewejS6h4wzQltmJfSW53N60G/ah9pngXGANdy9/aaE/TcUFpWosdm7JXS1WTQ==}
     cpu: [arm]
     os: [android]
 
   '@rollup/rollup-android-arm64@4.34.2':
-    resolution: {integrity: sha512-K5GfWe+vtQ3kyEbihrimM38UgX57UqHp+oME7X/EX9Im6suwZfa7Hsr8AtzbJvukTpwMGs+4s29YMSO3rwWtsw==, tarball: https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.2.tgz}
+    resolution: {integrity: sha512-K5GfWe+vtQ3kyEbihrimM38UgX57UqHp+oME7X/EX9Im6suwZfa7Hsr8AtzbJvukTpwMGs+4s29YMSO3rwWtsw==}
     cpu: [arm64]
     os: [android]
 
   '@rollup/rollup-darwin-arm64@4.34.2':
-    resolution: {integrity: sha512-PSN58XG/V/tzqDb9kDGutUruycgylMlUE59f40ny6QIRNsTEIZsrNQTJKUN2keMMSmlzgunMFqyaGLmly39sug==, tarball: https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.2.tgz}
+    resolution: {integrity: sha512-PSN58XG/V/tzqDb9kDGutUruycgylMlUE59f40ny6QIRNsTEIZsrNQTJKUN2keMMSmlzgunMFqyaGLmly39sug==}
     cpu: [arm64]
     os: [darwin]
 
   '@rollup/rollup-darwin-x64@4.34.2':
-    resolution: {integrity: sha512-gQhK788rQJm9pzmXyfBB84VHViDERhAhzGafw+E5mUpnGKuxZGkMVDa3wgDFKT6ukLC5V7QTifzsUKdNVxp5qQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.2.tgz}
+    resolution: {integrity: sha512-gQhK788rQJm9pzmXyfBB84VHViDERhAhzGafw+E5mUpnGKuxZGkMVDa3wgDFKT6ukLC5V7QTifzsUKdNVxp5qQ==}
     cpu: [x64]
     os: [darwin]
 
   '@rollup/rollup-freebsd-arm64@4.34.2':
-    resolution: {integrity: sha512-eiaHgQwGPpxLC3+zTAcdKl4VsBl3r0AiJOd1Um/ArEzAjN/dbPK1nROHrVkdnoE6p7Svvn04w3f/jEZSTVHunA==, tarball: https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.2.tgz}
+    resolution: {integrity: sha512-eiaHgQwGPpxLC3+zTAcdKl4VsBl3r0AiJOd1Um/ArEzAjN/dbPK1nROHrVkdnoE6p7Svvn04w3f/jEZSTVHunA==}
     cpu: [arm64]
     os: [freebsd]
 
   '@rollup/rollup-freebsd-x64@4.34.2':
-    resolution: {integrity: sha512-lhdiwQ+jf8pewYOTG4bag0Qd68Jn1v2gO1i0mTuiD+Qkt5vNfHVK/jrT7uVvycV8ZchlzXp5HDVmhpzjC6mh0g==, tarball: https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.2.tgz}
+    resolution: {integrity: sha512-lhdiwQ+jf8pewYOTG4bag0Qd68Jn1v2gO1i0mTuiD+Qkt5vNfHVK/jrT7uVvycV8ZchlzXp5HDVmhpzjC6mh0g==}
     cpu: [x64]
     os: [freebsd]
 
   '@rollup/rollup-linux-arm-gnueabihf@4.34.2':
-    resolution: {integrity: sha512-lfqTpWjSvbgQP1vqGTXdv+/kxIznKXZlI109WkIFPbud41bjigjNmOAAKoazmRGx+k9e3rtIdbq2pQZPV1pMig==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.2.tgz}
+    resolution: {integrity: sha512-lfqTpWjSvbgQP1vqGTXdv+/kxIznKXZlI109WkIFPbud41bjigjNmOAAKoazmRGx+k9e3rtIdbq2pQZPV1pMig==}
     cpu: [arm]
     os: [linux]
     libc: [glibc]
 
   '@rollup/rollup-linux-arm-musleabihf@4.34.2':
-    resolution: {integrity: sha512-RGjqULqIurqqv+NJTyuPgdZhka8ImMLB32YwUle2BPTDqDoXNgwFjdjQC59FbSk08z0IqlRJjrJ0AvDQ5W5lpw==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.2.tgz}
+    resolution: {integrity: sha512-RGjqULqIurqqv+NJTyuPgdZhka8ImMLB32YwUle2BPTDqDoXNgwFjdjQC59FbSk08z0IqlRJjrJ0AvDQ5W5lpw==}
     cpu: [arm]
     os: [linux]
     libc: [musl]
 
   '@rollup/rollup-linux-arm64-gnu@4.34.2':
-    resolution: {integrity: sha512-ZvkPiheyXtXlFqHpsdgscx+tZ7hoR59vOettvArinEspq5fxSDSgfF+L5wqqJ9R4t+n53nyn0sKxeXlik7AY9Q==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.2.tgz}
+    resolution: {integrity: sha512-ZvkPiheyXtXlFqHpsdgscx+tZ7hoR59vOettvArinEspq5fxSDSgfF+L5wqqJ9R4t+n53nyn0sKxeXlik7AY9Q==}
     cpu: [arm64]
     os: [linux]
     libc: [glibc]
 
   '@rollup/rollup-linux-arm64-musl@4.34.2':
-    resolution: {integrity: sha512-UlFk+E46TZEoxD9ufLKDBzfSG7Ki03fo6hsNRRRHF+KuvNZ5vd1RRVQm8YZlGsjcJG8R252XFK0xNPay+4WV7w==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.2.tgz}
+    resolution: {integrity: sha512-UlFk+E46TZEoxD9ufLKDBzfSG7Ki03fo6hsNRRRHF+KuvNZ5vd1RRVQm8YZlGsjcJG8R252XFK0xNPay+4WV7w==}
     cpu: [arm64]
     os: [linux]
     libc: [musl]
 
   '@rollup/rollup-linux-loongarch64-gnu@4.34.2':
-    resolution: {integrity: sha512-hJhfsD9ykx59jZuuoQgYT1GEcNNi3RCoEmbo5OGfG8RlHOiVS7iVNev9rhLKh7UBYq409f4uEw0cclTXx8nh8Q==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.2.tgz}
+    resolution: {integrity: sha512-hJhfsD9ykx59jZuuoQgYT1GEcNNi3RCoEmbo5OGfG8RlHOiVS7iVNev9rhLKh7UBYq409f4uEw0cclTXx8nh8Q==}
     cpu: [loong64]
     os: [linux]
     libc: [glibc]
 
   '@rollup/rollup-linux-powerpc64le-gnu@4.34.2':
-    resolution: {integrity: sha512-g/O5IpgtrQqPegvqopvmdCF9vneLE7eqYfdPWW8yjPS8f63DNam3U4ARL1PNNB64XHZDHKpvO2Giftf43puB8Q==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.2.tgz}
+    resolution: {integrity: sha512-g/O5IpgtrQqPegvqopvmdCF9vneLE7eqYfdPWW8yjPS8f63DNam3U4ARL1PNNB64XHZDHKpvO2Giftf43puB8Q==}
     cpu: [ppc64]
     os: [linux]
     libc: [glibc]
 
   '@rollup/rollup-linux-riscv64-gnu@4.34.2':
-    resolution: {integrity: sha512-bSQijDC96M6PuooOuXHpvXUYiIwsnDmqGU8+br2U7iPoykNi9JtMUpN7K6xml29e0evK0/g0D1qbAUzWZFHY5Q==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.2.tgz}
+    resolution: {integrity: sha512-bSQijDC96M6PuooOuXHpvXUYiIwsnDmqGU8+br2U7iPoykNi9JtMUpN7K6xml29e0evK0/g0D1qbAUzWZFHY5Q==}
     cpu: [riscv64]
     os: [linux]
     libc: [glibc]
 
   '@rollup/rollup-linux-s390x-gnu@4.34.2':
-    resolution: {integrity: sha512-49TtdeVAsdRuiUHXPrFVucaP4SivazetGUVH8CIxVsNsaPHV4PFkpLmH9LeqU/R4Nbgky9lzX5Xe1NrzLyraVA==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.2.tgz}
+    resolution: {integrity: sha512-49TtdeVAsdRuiUHXPrFVucaP4SivazetGUVH8CIxVsNsaPHV4PFkpLmH9LeqU/R4Nbgky9lzX5Xe1NrzLyraVA==}
     cpu: [s390x]
     os: [linux]
     libc: [glibc]
 
   '@rollup/rollup-linux-x64-gnu@4.34.2':
-    resolution: {integrity: sha512-j+jFdfOycLIQ7FWKka9Zd3qvsIyugg5LeZuHF6kFlXo6MSOc6R1w37YUVy8VpAKd81LMWGi5g9J25P09M0SSIw==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.2.tgz}
+    resolution: {integrity: sha512-j+jFdfOycLIQ7FWKka9Zd3qvsIyugg5LeZuHF6kFlXo6MSOc6R1w37YUVy8VpAKd81LMWGi5g9J25P09M0SSIw==}
     cpu: [x64]
     os: [linux]
     libc: [glibc]
 
   '@rollup/rollup-linux-x64-musl@4.34.2':
-    resolution: {integrity: sha512-aDPHyM/D2SpXfSNCVWCxyHmOqN9qb7SWkY1+vaXqMNMXslZYnwh9V/UCudl6psyG0v6Ukj7pXanIpfZwCOEMUg==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.2.tgz}
+    resolution: {integrity: sha512-aDPHyM/D2SpXfSNCVWCxyHmOqN9qb7SWkY1+vaXqMNMXslZYnwh9V/UCudl6psyG0v6Ukj7pXanIpfZwCOEMUg==}
     cpu: [x64]
     os: [linux]
     libc: [musl]
 
   '@rollup/rollup-win32-arm64-msvc@4.34.2':
-    resolution: {integrity: sha512-LQRkCyUBnAo7r8dbEdtNU08EKLCJMgAk2oP5H3R7BnUlKLqgR3dUjrLBVirmc1RK6U6qhtDw29Dimeer8d5hzQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.2.tgz}
+    resolution: {integrity: sha512-LQRkCyUBnAo7r8dbEdtNU08EKLCJMgAk2oP5H3R7BnUlKLqgR3dUjrLBVirmc1RK6U6qhtDw29Dimeer8d5hzQ==}
     cpu: [arm64]
     os: [win32]
 
   '@rollup/rollup-win32-ia32-msvc@4.34.2':
-    resolution: {integrity: sha512-wt8OhpQUi6JuPFkm1wbVi1BByeag87LDFzeKSXzIdGcX4bMLqORTtKxLoCbV57BHYNSUSOKlSL4BYYUghainYA==, tarball: https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.2.tgz}
+    resolution: {integrity: sha512-wt8OhpQUi6JuPFkm1wbVi1BByeag87LDFzeKSXzIdGcX4bMLqORTtKxLoCbV57BHYNSUSOKlSL4BYYUghainYA==}
     cpu: [ia32]
     os: [win32]
 
   '@rollup/rollup-win32-x64-msvc@4.34.2':
-    resolution: {integrity: sha512-rUrqINax0TvrPBXrFKg0YbQx18NpPN3NNrgmaao9xRNbTwek7lOXObhx8tQy8gelmQ/gLaGy1WptpU2eKJZImg==, tarball: https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.2.tgz}
+    resolution: {integrity: sha512-rUrqINax0TvrPBXrFKg0YbQx18NpPN3NNrgmaao9xRNbTwek7lOXObhx8tQy8gelmQ/gLaGy1WptpU2eKJZImg==}
     cpu: [x64]
     os: [win32]
 
@@ -617,7 +620,7 @@ packages:
     engines: {node: '>=18'}
 
   '@sxzz/popperjs-es@2.11.7':
-    resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==, tarball: https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz}
+    resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==}
 
   '@tsconfig/node22@22.0.0':
     resolution: {integrity: sha512-twLQ77zevtxobBOD4ToAtVmuYrpeYUh3qh+TEp+08IWhpsrIflVHqQ1F1CiPxQGL7doCdBIOOCF+1Tm833faNg==}
@@ -850,7 +853,7 @@ packages:
     resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
 
   base64-arraybuffer@1.0.2:
-    resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==, tarball: https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz}
+    resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
     engines: {node: '>= 0.6.0'}
 
   birpc@0.2.19:
@@ -875,7 +878,7 @@ packages:
     hasBin: true
 
   buffer-builder@0.2.0:
-    resolution: {integrity: sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==, tarball: https://registry.npmmirror.com/buffer-builder/-/buffer-builder-0.2.0.tgz}
+    resolution: {integrity: sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==}
 
   bundle-name@4.1.0:
     resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
@@ -900,7 +903,7 @@ packages:
     resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
 
   colorjs.io@0.5.2:
-    resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==, tarball: https://registry.npmmirror.com/colorjs.io/-/colorjs.io-0.5.2.tgz}
+    resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==}
 
   combined-stream@1.0.8:
     resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
@@ -921,7 +924,7 @@ packages:
     engines: {node: '>= 8'}
 
   css-line-break@2.1.0:
-    resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==, tarball: https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz}
+    resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==}
 
   cssesc@3.0.0:
     resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
@@ -966,7 +969,7 @@ packages:
     engines: {node: '>=0.4.0'}
 
   echarts@5.6.0:
-    resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==, tarball: https://registry.npmmirror.com/echarts/-/echarts-5.6.0.tgz}
+    resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==}
 
   electron-to-chromium@1.5.92:
     resolution: {integrity: sha512-BeHgmNobs05N1HMmMZ7YIuHfYBGlq/UmvlsTgg+fsbFs9xVMj+xJHFg19GN04+9Q+r8Xnh9LXqaYIyEWElnNgQ==}
@@ -1110,7 +1113,7 @@ packages:
     engines: {node: '>=16.0.0'}
 
   file-saver@2.0.5:
-    resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==, tarball: https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz}
+    resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
 
   fill-range@7.1.1:
     resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
@@ -1145,7 +1148,7 @@ packages:
     engines: {node: '>=14.14'}
 
   fsevents@2.3.3:
-    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, tarball: https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz}
+    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
     engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
     os: [darwin]
 
@@ -1199,7 +1202,7 @@ packages:
     engines: {node: '>=8'}
 
   html2canvas@1.4.1:
-    resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==, tarball: https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz}
+    resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
     engines: {node: '>=8.0.0'}
 
   human-signals@8.0.0:
@@ -1211,7 +1214,7 @@ packages:
     engines: {node: '>= 4'}
 
   immutable@5.0.3:
-    resolution: {integrity: sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==, tarball: https://registry.npmmirror.com/immutable/-/immutable-5.0.3.tgz}
+    resolution: {integrity: sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==}
 
   import-fresh@3.3.1:
     resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
@@ -1274,6 +1277,10 @@ packages:
     resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
     hasBin: true
 
+  js-cookie@3.0.5:
+    resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
+    engines: {node: '>=14'}
+
   js-tokens@4.0.0:
     resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
 
@@ -1545,130 +1552,130 @@ packages:
     resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
 
   rxjs@7.8.1:
-    resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==, tarball: https://registry.npmmirror.com/rxjs/-/rxjs-7.8.1.tgz}
+    resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
 
   sass-embedded-android-arm64@1.83.4:
-    resolution: {integrity: sha512-tgX4FzmbVqnQmD67ZxQDvI+qFNABrboOQgwsG05E5bA/US42zGajW9AxpECJYiMXVOHmg+d81ICbjb0fsVHskw==, tarball: https://registry.npmmirror.com/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.83.4.tgz}
+    resolution: {integrity: sha512-tgX4FzmbVqnQmD67ZxQDvI+qFNABrboOQgwsG05E5bA/US42zGajW9AxpECJYiMXVOHmg+d81ICbjb0fsVHskw==}
     engines: {node: '>=14.0.0'}
     cpu: [arm64]
     os: [android]
 
   sass-embedded-android-arm@1.83.4:
-    resolution: {integrity: sha512-9Z4pJAOgEkXa3VDY/o+U6l5XvV0mZTJcSl0l/mSPHihjAHSpLYnOW6+KOWeM8dxqrsqTYcd6COzhanI/a++5Gw==, tarball: https://registry.npmmirror.com/sass-embedded-android-arm/-/sass-embedded-android-arm-1.83.4.tgz}
+    resolution: {integrity: sha512-9Z4pJAOgEkXa3VDY/o+U6l5XvV0mZTJcSl0l/mSPHihjAHSpLYnOW6+KOWeM8dxqrsqTYcd6COzhanI/a++5Gw==}
     engines: {node: '>=14.0.0'}
     cpu: [arm]
     os: [android]
 
   sass-embedded-android-ia32@1.83.4:
-    resolution: {integrity: sha512-RsFOziFqPcfZXdFRULC4Ayzy9aK6R6FwQ411broCjlOBX+b0gurjRadkue3cfUEUR5mmy0KeCbp7zVKPLTK+5Q==, tarball: https://registry.npmmirror.com/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.83.4.tgz}
+    resolution: {integrity: sha512-RsFOziFqPcfZXdFRULC4Ayzy9aK6R6FwQ411broCjlOBX+b0gurjRadkue3cfUEUR5mmy0KeCbp7zVKPLTK+5Q==}
     engines: {node: '>=14.0.0'}
     cpu: [ia32]
     os: [android]
 
   sass-embedded-android-riscv64@1.83.4:
-    resolution: {integrity: sha512-EHwh0nmQarBBrMRU928eTZkFGx19k/XW2YwbPR4gBVdWLkbTgCA5aGe8hTE6/1zStyx++3nDGvTZ78+b/VvvLg==, tarball: https://registry.npmmirror.com/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.83.4.tgz}
+    resolution: {integrity: sha512-EHwh0nmQarBBrMRU928eTZkFGx19k/XW2YwbPR4gBVdWLkbTgCA5aGe8hTE6/1zStyx++3nDGvTZ78+b/VvvLg==}
     engines: {node: '>=14.0.0'}
     cpu: [riscv64]
     os: [android]
 
   sass-embedded-android-x64@1.83.4:
-    resolution: {integrity: sha512-0PgQNuPWYy1jEOEPDVsV89KfqOsMLIp9CSbjBY7jRcwRhyVAcigqrUG6bDeNtojHUYKA1kU+Eh/85WxOHUOgBw==, tarball: https://registry.npmmirror.com/sass-embedded-android-x64/-/sass-embedded-android-x64-1.83.4.tgz}
+    resolution: {integrity: sha512-0PgQNuPWYy1jEOEPDVsV89KfqOsMLIp9CSbjBY7jRcwRhyVAcigqrUG6bDeNtojHUYKA1kU+Eh/85WxOHUOgBw==}
     engines: {node: '>=14.0.0'}
     cpu: [x64]
     os: [android]
 
   sass-embedded-darwin-arm64@1.83.4:
-    resolution: {integrity: sha512-rp2ywymWc3nymnSnAFG5R/8hvxWCsuhK3wOnD10IDlmNB7o4rzKby1c+2ZfpQGowlYGWsWWTgz8FW2qzmZsQRw==, tarball: https://registry.npmmirror.com/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.83.4.tgz}
+    resolution: {integrity: sha512-rp2ywymWc3nymnSnAFG5R/8hvxWCsuhK3wOnD10IDlmNB7o4rzKby1c+2ZfpQGowlYGWsWWTgz8FW2qzmZsQRw==}
     engines: {node: '>=14.0.0'}
     cpu: [arm64]
     os: [darwin]
 
   sass-embedded-darwin-x64@1.83.4:
-    resolution: {integrity: sha512-kLkN2lXz9PCgGfDS8Ev5YVcl/V2173L6379en/CaFuJJi7WiyPgBymW7hOmfCt4uO4R1y7CP2Uc08DRtZsBlAA==, tarball: https://registry.npmmirror.com/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.83.4.tgz}
+    resolution: {integrity: sha512-kLkN2lXz9PCgGfDS8Ev5YVcl/V2173L6379en/CaFuJJi7WiyPgBymW7hOmfCt4uO4R1y7CP2Uc08DRtZsBlAA==}
     engines: {node: '>=14.0.0'}
     cpu: [x64]
     os: [darwin]
 
   sass-embedded-linux-arm64@1.83.4:
-    resolution: {integrity: sha512-E0zjsZX2HgESwyqw31EHtI39DKa7RgK7nvIhIRco1d0QEw227WnoR9pjH3M/ZQy4gQj3GKilOFHM5Krs/omeIA==, tarball: https://registry.npmmirror.com/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.83.4.tgz}
+    resolution: {integrity: sha512-E0zjsZX2HgESwyqw31EHtI39DKa7RgK7nvIhIRco1d0QEw227WnoR9pjH3M/ZQy4gQj3GKilOFHM5Krs/omeIA==}
     engines: {node: '>=14.0.0'}
     cpu: [arm64]
     os: [linux]
 
   sass-embedded-linux-arm@1.83.4:
-    resolution: {integrity: sha512-nL90ryxX2lNmFucr9jYUyHHx21AoAgdCL1O5Ltx2rKg2xTdytAGHYo2MT5S0LIeKLa/yKP/hjuSvrbICYNDvtA==, tarball: https://registry.npmmirror.com/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.83.4.tgz}
+    resolution: {integrity: sha512-nL90ryxX2lNmFucr9jYUyHHx21AoAgdCL1O5Ltx2rKg2xTdytAGHYo2MT5S0LIeKLa/yKP/hjuSvrbICYNDvtA==}
     engines: {node: '>=14.0.0'}
     cpu: [arm]
     os: [linux]
 
   sass-embedded-linux-ia32@1.83.4:
-    resolution: {integrity: sha512-ew5HpchSzgAYbQoriRh8QhlWn5Kw2nQ2jHoV9YLwGKe3fwwOWA0KDedssvDv7FWnY/FCqXyymhLd6Bxae4Xquw==, tarball: https://registry.npmmirror.com/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.83.4.tgz}
+    resolution: {integrity: sha512-ew5HpchSzgAYbQoriRh8QhlWn5Kw2nQ2jHoV9YLwGKe3fwwOWA0KDedssvDv7FWnY/FCqXyymhLd6Bxae4Xquw==}
     engines: {node: '>=14.0.0'}
     cpu: [ia32]
     os: [linux]
 
   sass-embedded-linux-musl-arm64@1.83.4:
-    resolution: {integrity: sha512-IzMgalf6MZOxgp4AVCgsaWAFDP/IVWOrgVXxkyhw29fyAEoSWBJH4k87wyPhEtxSuzVHLxKNbc8k3UzdWmlBFg==, tarball: https://registry.npmmirror.com/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.83.4.tgz}
+    resolution: {integrity: sha512-IzMgalf6MZOxgp4AVCgsaWAFDP/IVWOrgVXxkyhw29fyAEoSWBJH4k87wyPhEtxSuzVHLxKNbc8k3UzdWmlBFg==}
     engines: {node: '>=14.0.0'}
     cpu: [arm64]
     os: [linux]
 
   sass-embedded-linux-musl-arm@1.83.4:
-    resolution: {integrity: sha512-0RrJRwMrmm+gG0VOB5b5Cjs7Sd+lhqpQJa6EJNEaZHljJokEfpE5GejZsGMRMIQLxEvVphZnnxl6sonCGFE/QQ==, tarball: https://registry.npmmirror.com/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.83.4.tgz}
+    resolution: {integrity: sha512-0RrJRwMrmm+gG0VOB5b5Cjs7Sd+lhqpQJa6EJNEaZHljJokEfpE5GejZsGMRMIQLxEvVphZnnxl6sonCGFE/QQ==}
     engines: {node: '>=14.0.0'}
     cpu: [arm]
     os: [linux]
 
   sass-embedded-linux-musl-ia32@1.83.4:
-    resolution: {integrity: sha512-LLb4lYbcxPzX4UaJymYXC+WwokxUlfTJEFUv5VF0OTuSsHAGNRs/rslPtzVBTvMeG9TtlOQDhku1F7G6iaDotA==, tarball: https://registry.npmmirror.com/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.83.4.tgz}
+    resolution: {integrity: sha512-LLb4lYbcxPzX4UaJymYXC+WwokxUlfTJEFUv5VF0OTuSsHAGNRs/rslPtzVBTvMeG9TtlOQDhku1F7G6iaDotA==}
     engines: {node: '>=14.0.0'}
     cpu: [ia32]
     os: [linux]
 
   sass-embedded-linux-musl-riscv64@1.83.4:
-    resolution: {integrity: sha512-zoKlPzD5Z13HKin1UGR74QkEy+kZEk2AkGX5RelRG494mi+IWwRuWCppXIovor9+BQb9eDWPYPoMVahwN5F7VA==, tarball: https://registry.npmmirror.com/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.83.4.tgz}
+    resolution: {integrity: sha512-zoKlPzD5Z13HKin1UGR74QkEy+kZEk2AkGX5RelRG494mi+IWwRuWCppXIovor9+BQb9eDWPYPoMVahwN5F7VA==}
     engines: {node: '>=14.0.0'}
     cpu: [riscv64]
     os: [linux]
 
   sass-embedded-linux-musl-x64@1.83.4:
-    resolution: {integrity: sha512-hB8+/PYhfEf2zTIcidO5Bpof9trK6WJjZ4T8g2MrxQh8REVtdPcgIkoxczRynqybf9+fbqbUwzXtiUao2GV+vQ==, tarball: https://registry.npmmirror.com/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.83.4.tgz}
+    resolution: {integrity: sha512-hB8+/PYhfEf2zTIcidO5Bpof9trK6WJjZ4T8g2MrxQh8REVtdPcgIkoxczRynqybf9+fbqbUwzXtiUao2GV+vQ==}
     engines: {node: '>=14.0.0'}
     cpu: [x64]
     os: [linux]
 
   sass-embedded-linux-riscv64@1.83.4:
-    resolution: {integrity: sha512-83fL4n+oeDJ0Y4KjASmZ9jHS1Vl9ESVQYHMhJE0i4xDi/P3BNarm2rsKljq/QtrwGpbqwn8ujzOu7DsNCMDSHA==, tarball: https://registry.npmmirror.com/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.83.4.tgz}
+    resolution: {integrity: sha512-83fL4n+oeDJ0Y4KjASmZ9jHS1Vl9ESVQYHMhJE0i4xDi/P3BNarm2rsKljq/QtrwGpbqwn8ujzOu7DsNCMDSHA==}
     engines: {node: '>=14.0.0'}
     cpu: [riscv64]
     os: [linux]
 
   sass-embedded-linux-x64@1.83.4:
-    resolution: {integrity: sha512-NlnGdvCmTD5PK+LKXlK3sAuxOgbRIEoZfnHvxd157imCm/s2SYF/R28D0DAAjEViyI8DovIWghgbcqwuertXsA==, tarball: https://registry.npmmirror.com/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.83.4.tgz}
+    resolution: {integrity: sha512-NlnGdvCmTD5PK+LKXlK3sAuxOgbRIEoZfnHvxd157imCm/s2SYF/R28D0DAAjEViyI8DovIWghgbcqwuertXsA==}
     engines: {node: '>=14.0.0'}
     cpu: [x64]
     os: [linux]
 
   sass-embedded-win32-arm64@1.83.4:
-    resolution: {integrity: sha512-J2BFKrEaeSrVazU2qTjyQdAk+MvbzJeTuCET0uAJEXSKtvQ3AzxvzndS7LqkDPbF32eXAHLw8GVpwcBwKbB3Uw==, tarball: https://registry.npmmirror.com/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.83.4.tgz}
+    resolution: {integrity: sha512-J2BFKrEaeSrVazU2qTjyQdAk+MvbzJeTuCET0uAJEXSKtvQ3AzxvzndS7LqkDPbF32eXAHLw8GVpwcBwKbB3Uw==}
     engines: {node: '>=14.0.0'}
     cpu: [arm64]
     os: [win32]
 
   sass-embedded-win32-ia32@1.83.4:
-    resolution: {integrity: sha512-uPAe9T/5sANFhJS5dcfAOhOJy8/l2TRYG4r+UO3Wp4yhqbN7bggPvY9c7zMYS0OC8tU/bCvfYUDFHYMCl91FgA==, tarball: https://registry.npmmirror.com/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.83.4.tgz}
+    resolution: {integrity: sha512-uPAe9T/5sANFhJS5dcfAOhOJy8/l2TRYG4r+UO3Wp4yhqbN7bggPvY9c7zMYS0OC8tU/bCvfYUDFHYMCl91FgA==}
     engines: {node: '>=14.0.0'}
     cpu: [ia32]
     os: [win32]
 
   sass-embedded-win32-x64@1.83.4:
-    resolution: {integrity: sha512-C9fkDY0jKITdJFij4UbfPFswxoXN9O/Dr79v17fJnstVwtUojzVJWKHUXvF0Zg2LIR7TCc4ju3adejKFxj7ueA==, tarball: https://registry.npmmirror.com/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.83.4.tgz}
+    resolution: {integrity: sha512-C9fkDY0jKITdJFij4UbfPFswxoXN9O/Dr79v17fJnstVwtUojzVJWKHUXvF0Zg2LIR7TCc4ju3adejKFxj7ueA==}
     engines: {node: '>=14.0.0'}
     cpu: [x64]
     os: [win32]
 
   sass-embedded@1.83.4:
-    resolution: {integrity: sha512-Hf2burRA/y5PGxsg6jB9UpoK/xZ6g/pgrkOcdl6j+rRg1Zj8XhGKZ1MTysZGtTPUUmiiErqzkP5+Kzp95yv9GQ==, tarball: https://registry.npmmirror.com/sass-embedded/-/sass-embedded-1.83.4.tgz}
+    resolution: {integrity: sha512-Hf2burRA/y5PGxsg6jB9UpoK/xZ6g/pgrkOcdl6j+rRg1Zj8XhGKZ1MTysZGtTPUUmiiErqzkP5+Kzp95yv9GQ==}
     engines: {node: '>=16.0.0'}
     hasBin: true
 
@@ -1726,18 +1733,18 @@ packages:
     engines: {node: '>=8'}
 
   supports-color@8.1.1:
-    resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==, tarball: https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz}
+    resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
     engines: {node: '>=10'}
 
   svg-tags@1.0.0:
     resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==}
 
   sync-child-process@1.0.2:
-    resolution: {integrity: sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==, tarball: https://registry.npmmirror.com/sync-child-process/-/sync-child-process-1.0.2.tgz}
+    resolution: {integrity: sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==}
     engines: {node: '>=16.0.0'}
 
   sync-message-port@1.1.3:
-    resolution: {integrity: sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==, tarball: https://registry.npmmirror.com/sync-message-port/-/sync-message-port-1.1.3.tgz}
+    resolution: {integrity: sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==}
     engines: {node: '>=16.0.0'}
 
   synckit@0.9.2:
@@ -1745,10 +1752,10 @@ packages:
     engines: {node: ^14.18.0 || >=16.0.0}
 
   text-segmentation@1.0.3:
-    resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==, tarball: https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz}
+    resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
 
   three@0.175.0:
-    resolution: {integrity: sha512-nNE3pnTHxXN/Phw768u0Grr7W4+rumGg/H6PgeseNJojkJtmeHJfZWi41Gp2mpXl1pg1pf1zjwR4McM1jTqkpg==, tarball: https://registry.npmmirror.com/three/-/three-0.175.0.tgz}
+    resolution: {integrity: sha512-nNE3pnTHxXN/Phw768u0Grr7W4+rumGg/H6PgeseNJojkJtmeHJfZWi41Gp2mpXl1pg1pf1zjwR4McM1jTqkpg==}
 
   to-regex-range@5.0.1:
     resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
@@ -1759,7 +1766,7 @@ packages:
     engines: {node: '>=6'}
 
   troisjs@0.3.4:
-    resolution: {integrity: sha512-aonG2lw+QyTjytJpvYIaGv2AiIKzm+eSYT4ByG64uVh+jCLAWxnblS6bCewIckvCMqs2j3z351Q/IFIPWYViCQ==, tarball: https://registry.npmmirror.com/troisjs/-/troisjs-0.3.4.tgz}
+    resolution: {integrity: sha512-aonG2lw+QyTjytJpvYIaGv2AiIKzm+eSYT4ByG64uVh+jCLAWxnblS6bCewIckvCMqs2j3z351Q/IFIPWYViCQ==}
 
   ts-api-utils@2.0.1:
     resolution: {integrity: sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==}
@@ -1768,7 +1775,7 @@ packages:
       typescript: '>=4.8.4'
 
   tslib@2.3.0:
-    resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==, tarball: https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz}
+    resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
 
   tslib@2.8.1:
     resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
@@ -1817,10 +1824,10 @@ packages:
     resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
 
   utrie@1.0.2:
-    resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==, tarball: https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz}
+    resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==}
 
   varint@6.0.0:
-    resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==, tarball: https://registry.npmmirror.com/varint/-/varint-6.0.0.tgz}
+    resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==}
 
   vite-hot-client@0.2.4:
     resolution: {integrity: sha512-a1nzURqO7DDmnXqabFOliz908FRmIppkBKsJthS8rbe8hBEXwEwe4C3Pp33Z1JoFCYfVL4kTOMLKk0ZZxREIeA==}
@@ -1957,7 +1964,7 @@ packages:
     engines: {node: '>=18'}
 
   zrender@5.6.1:
-    resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==, tarball: https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz}
+    resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==}
 
 snapshots:
 
@@ -3168,6 +3175,8 @@ snapshots:
 
   jiti@2.4.2: {}
 
+  js-cookie@3.0.5: {}
+
   js-tokens@4.0.0: {}
 
   js-yaml@4.1.0:

+ 7 - 2
viewer/src/api/axios.js

@@ -20,11 +20,17 @@ api.interceptors.request.use(async config => {
         config.headers['X-CSRFToken'] = csrfToken;
         if(userToken !== ""){
             config.headers.Authorization = 'Token ' + userToken;
+        }else{
+            userToken = sessionStorage.getItem('userToken')
+            config.headers.Authorization = 'Token ' + userToken;
         }
     }
     if (method === 'get'){
         if(userToken !== ""){
             config.headers.Authorization = 'Token ' + userToken;
+        }else{
+            userToken = sessionStorage.getItem('userToken')
+            config.headers.Authorization = 'Token ' + userToken;
         }
     }
     return config;
@@ -38,12 +44,11 @@ async function getCSRFToken(url) {
     }else{
         return csrfToken;
     }
-    
-    
 }
 
 export function setUserToken(token) {
     userToken = token;
+    sessionStorage.setItem('userToken', token)
     console.log("TOKEN is " + token);
 }
 

+ 4 - 0
viewer/src/store/userInfo.js

@@ -25,6 +25,8 @@ export function useUserInfo() {
                     }
                 )
                 console.log(userInfo.value)
+                // 将userInfo存入sessionStorage
+                sessionStorage.setItem('userInfo', userInfo.value);
                 registerResult.status = 'success';
                 registerResult.message = '注册成功';
                 registerResult.data = userInfo;
@@ -70,6 +72,8 @@ export function useUserInfo() {
                     }
                 )
                 console.log(userInfo.value)
+                // 将userInfo存入sessionStorage
+                sessionStorage.setItem('userInfo', userInfo.value);
                 loginResult.status = 'success';
                 loginResult.message = '登录成功';
                 loginResult.data = userInfo;

+ 27 - 5
viewer/src/views/dashoard/calculate.vue

@@ -88,7 +88,7 @@
   </div>
 </template>
 <script setup>
-import { inject, onMounted, ref, computed, defineAsyncComponent, shallowRef } from 'vue'
+import { inject, onMounted, onUnmounted, ref, computed, defineAsyncComponent, shallowRef } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { postData, getData } from '@/api/axios.js'
 import {
@@ -143,13 +143,14 @@ const progressStyle = (index) => {
 
 // 获取基础颜色
 const getBaseColor = (value) => {
-  if (value >= 100) return 'rgba(100, 255, 100, 0.3)' // 绿色
-  return 'rgba(66, 144, 255, 0.3)' // 蓝色
+  if (value >= 100) return 'rgba(100, 255, 100, 0.3)' // 绿色,表示任务完成
+  if (value < 0) return 'rgba(255, 20, 20, 0.3)' // 红色,表示任务失败
+  return 'rgba(66, 144, 255, 0.3)' // 蓝色,任务执行中,按比例显示
 }
 
 // 生成渐变背景
 const generateGradient = (value, color) => {
-  if (value === 0 || value >= 100) return 'none'
+  if (value <= 0 || value >= 100) return 'none' // 任务失败或任务完成都是完整显示,当存在不满进度时逐渐填满
 
   const percentage = `${Math.min(value, 100)}%`
   return `linear-gradient(
@@ -277,7 +278,10 @@ const startCalculate = async () => {
       // 成功获取进度更新信息,将进度赋值给progress
       console.log(response)
       if (response.status === "success") {
+        // 任务是否全部完成
         let missionComplete = true
+        // 任务是否发生失败
+        let missionFailed = false
         response.data.forEach(result => {
           // 查找序号,根据序号修改progress
           const index = plans.value.findIndex(p => p.id === result.planId)
@@ -286,6 +290,12 @@ const startCalculate = async () => {
           if (result.progress !== 100) {
             missionComplete = false
           }
+          if (result.progress == -1 || result.progress == -2){
+            // -1表示单个任务中止
+
+            // -2表示整个任务中止
+
+          }
         })
         if (missionComplete) {
           ElMessageBox.alert("任务已全部完成", "", {
@@ -300,7 +310,7 @@ const startCalculate = async () => {
       console.log("catch error when start interval", error)
       clearInterval(progressInterval.value)
     }
-  }, 5000)
+  }, 2000) // 2s更新一次
 }
 
 const pauseCalculate = () => {
@@ -311,6 +321,8 @@ const pauseCalculate = () => {
   } catch (error) {
     console.log(error)
   }
+  //暂停时需要发送暂停请求
+  // todos
 }
 
 const stopCalculate = () => {
@@ -321,12 +333,22 @@ const stopCalculate = () => {
   } catch (error) {
     console.log(error)
   }
+  //停止时需要发送停止请求
+  // todos
 }
 
 onMounted(() => {
   setTimeout(() => { updateConnections() }, 500)
 })
 
+onUnmounted(() => {
+  try {
+    clearInterval(progressInterval.value)
+  } catch (error) {
+    console.log(error)
+  }
+})
+
 </script>
 <style lang="scss" scoped>
 .container {