diff --git a/app.yml b/app.yml index 2349e18..bf10054 100644 --- a/app.yml +++ b/app.yml @@ -1,15 +1,16 @@ app_code: bk_gsekit app_name: 进程配置管理 -app_name_en: bk-gsekit +app_name_en: GSEKit is_use_celery: true author: 蓝鲸智云 language: Python -introduction: 进程配置管理 -introduction_en: Process&config manager +introduction: 进程配置管理是腾讯蓝鲸智云推出的一个专注于进程和配置文件管理的 SaaS 工具。 +introduction_en: Process&config manager is a SaaS tool launched by Tencent BlueKing + that focuses on process and configuration file management. description: 进程配置管理是腾讯蓝鲸智云推出的一个专注于进程和配置文件管理的 SaaS 工具。 description_en: Process&config manager is a SaaS tool launched by Tencent BlueKing that focuses on process and configuration file management. -version: 1.0.31 +version: 1.0.32 category: 运维工具 language_support: 英语,中文 desktop: diff --git a/app_desc.yaml b/app_desc.yaml index 30a0c5b..b0dec6c 100644 --- a/app_desc.yaml +++ b/app_desc.yaml @@ -4,11 +4,11 @@ app: region: default bk_app_code: "bk_gsekit" bk_app_name: 进程配置管理 - bk_app_name_en: bk-gsekit + bk_app_name_en: GSEKit market: category: 运维工具 - introduction: 进程配置管理 - introduction_en: Process&config manager + introduction: 进程配置管理是腾讯蓝鲸智云推出的一个专注于进程和配置文件管理的 SaaS 工具。 + introduction_en: Process&config manager is a SaaS tool launched by Tencent BlueKing that focuses on process and configuration file management. description: 进程配置管理是腾讯蓝鲸智云推出的一个专注于进程和配置文件管理的 SaaS 工具。 description_en: Process&config manager is a SaaS tool launched by Tencent BlueKing that focuses on process and configuration file management. display_options: diff --git a/apps/gsekit/configfile/handlers/config_template.py b/apps/gsekit/configfile/handlers/config_template.py index e28aad5..2001d65 100644 --- a/apps/gsekit/configfile/handlers/config_template.py +++ b/apps/gsekit/configfile/handlers/config_template.py @@ -407,17 +407,19 @@ def fill_with_is_bound(cls, config_templates: List[Dict]) -> List[Dict]: config_version_count["config_template_id"]: config_version_count["config_version_count"] for config_version_count in config_version_counts } - - has_release_config_tmpl_ids = set( - ConfigInstance.objects.filter(config_template_id__in=config_template_ids, is_released=True).values_list( - "config_template_id", flat=True - ) - ) + # 返回数量太多出现慢查询 + # has_release_config_tmpl_ids = set( + # ConfigInstance.objects.filter(config_template_id__in=config_template_ids, is_released=True).values_list( + # "config_template_id", flat=True + # ) + # ) for config_template in config_templates: config_template_id = config_template["config_template_id"] relation_count = config_template_binding_count_map[config_template_id] config_template["relation_count"] = relation_count config_template["is_bound"] = bool(sum(relation_count.values())) - config_template["has_release"] = config_template_id in has_release_config_tmpl_ids + config_template["has_release"] = ConfigInstance.objects.filter( + config_template_id=config_template_id, is_released=True + ).exists() config_template["has_version"] = bool(config_template_version_map.get(config_template_id, 0)) return config_templates diff --git a/apps/gsekit/meta/models.py b/apps/gsekit/meta/models.py index 6f7a09a..aa8efef 100644 --- a/apps/gsekit/meta/models.py +++ b/apps/gsekit/meta/models.py @@ -37,6 +37,7 @@ class KEYS: SYNC_BIZ_PROCESS_STATUS_TIMEOUT = "SYNC_BIZ_PROCESS_STATUS_TIMEOUT" # 记录所有业务ID,用于同步新业务到灰度列表对比使用 ALL_BIZ_IDS = "ALL_BIZ_IDS" + SYNC_PROC_STATUS_TIME = "SYNC_PROC_STATUS_TIME" @classmethod def process_task_aggregate_info(cls, bk_biz_id: int) -> typing.Dict[str, str]: diff --git a/apps/gsekit/periodic_tasks/sync_process.py b/apps/gsekit/periodic_tasks/sync_process.py index 598b419..182da15 100644 --- a/apps/gsekit/periodic_tasks/sync_process.py +++ b/apps/gsekit/periodic_tasks/sync_process.py @@ -25,7 +25,10 @@ @task(ignore_result=True) def sync_biz_process_task(bk_biz_id): - ProcessHandler(bk_biz_id=bk_biz_id).sync_biz_process() + logger.info(f"[sync_biz_process_task] start, bk_biz_id={bk_biz_id}") + process_related_infos = ProcessHandler(bk_biz_id=bk_biz_id).sync_biz_process() + ProcessHandler(bk_biz_id=bk_biz_id).sync_biz_process_status(process_related_infos=process_related_infos) + logger.info(f"[sync_biz_process_task] finished, bk_biz_id={bk_biz_id}") @periodic_task(run_every=django_celery_beat.tzcrontab.TzAwareCrontab(minute="*/10", tz=timezone.get_current_timezone())) @@ -38,10 +41,8 @@ def sync_process(bk_biz_id=None): count = len(bk_biz_id_list) for index, biz_id in enumerate(bk_biz_id_list): logger.info(f"[sync_process] start, bk_biz_id={biz_id}") - countdown = calculate_countdown(count, index) + countdown = calculate_countdown(count, index) if count > 1 else 0 sync_biz_process_task.apply_async((biz_id,), countdown=countdown) - # TODO 由于GSE接口存在延迟,此处暂停同步状态的周期任务,待GSE优化后再开启 - # ProcessHandler(bk_biz_id=biz_id).sync_proc_status_to_db() logger.info(f"[sync_process] bk_biz_id={biz_id} will be run after {countdown} seconds.") @@ -54,9 +55,9 @@ def sync_new_biz_to_gray_scope_list(): logger.info(f"sync_new_biz_to_gray_scope_list: {task_id} Start adding new biz to GSE2_GRAY_SCOPE_LIST.") all_biz_ids = GlobalSettings.get_config(key=GlobalSettings.KEYS.ALL_BIZ_IDS, default=[]) - if not all_biz_ids: - logger.info(f"sync_new_biz_to_gray_scope_list: {task_id} No need to add new biz to GSE2_GRAY_SCOPE_LIST.") - return None + # if not all_biz_ids: + # logger.info(f"sync_new_biz_to_gray_scope_list: {task_id} No need to add new biz to GSE2_GRAY_SCOPE_LIST.") + # return None cc_all_biz_ids: List[int] = list(CMDBHandler.biz_id_name_without_permission().keys()) new_biz_ids: List[int] = list(set(cc_all_biz_ids) - set(all_biz_ids)) @@ -65,7 +66,10 @@ def sync_new_biz_to_gray_scope_list(): if new_biz_ids: with transaction.atomic(): # 更新全部业务列表 - GlobalSettings.update_config(key=GlobalSettings.KEYS.ALL_BIZ_IDS, value=cc_all_biz_ids) + if GlobalSettings.objects.filter(key=GlobalSettings.KEYS.ALL_BIZ_IDS): + GlobalSettings.update_config(key=GlobalSettings.KEYS.ALL_BIZ_IDS, value=cc_all_biz_ids) + else: + GlobalSettings.set_config(key=GlobalSettings.KEYS.ALL_BIZ_IDS, value=cc_all_biz_ids) # 对新业务执行灰度操作 result = GrayHandler.build({"bk_biz_ids": new_biz_ids}) diff --git a/apps/gsekit/pipeline_plugins/components/collections/gse.py b/apps/gsekit/pipeline_plugins/components/collections/gse.py index 3c8e442..e780d4f 100644 --- a/apps/gsekit/pipeline_plugins/components/collections/gse.py +++ b/apps/gsekit/pipeline_plugins/components/collections/gse.py @@ -10,7 +10,7 @@ """ import json import logging -from typing import Dict +from typing import Any, Dict, List from django.db.models import F from django.utils.translation import ugettext as _ @@ -27,6 +27,8 @@ from apps.gsekit.process.models import Process, ProcessInst from apps.utils.mako_utils.render import mako_render from dataclasses import dataclass + +from env.constants import GseVersion from .base import CommonData from apps.adapters.api.gse import get_gse_api_helper from apps.adapters.api.gse.base import GseApiBaseHelper @@ -269,10 +271,24 @@ def _execute(self, data, parent_data, common_data): op_type = data.get_one_of_inputs("op_type") data.outputs.proc_op_status_map = {} - proc_operate_req = [] + proc_operate_req: Dict[str, Dict[str, Any]] = {} + no_agent_id_job_task_ids: List[int] = [] for job_task in job_tasks: host_info = job_task.extra_data["process_info"]["host"] + if all([common_data.gse_api_helper.version == GseVersion.V2.value, not host_info.get("bk_agent_id", "")]): + # 对于GseV2来说必须使用agentid进行操作,如果没有agentid可能会导致任务整个handling + no_agent_id_job_task_ids.append(job_task.id) + error_msg = _("该主机无bk_agent_id无法进行相关操作, 请检查主机Agent是否正常") + job_task.set_status( + JobStatus.FAILED, + extra_data={ + "failed_reason": self.generate_proc_op_error_msg(GseDataErrorCode.OP_FAILED, error_msg), + "err_code": GseDataErrorCode.OP_FAILED, + }, + ) + continue + process_info = job_task.extra_data["process_info"]["process"] set_info = job_task.extra_data["process_info"]["set"] module_info = job_task.extra_data["process_info"]["module"] @@ -298,24 +314,24 @@ def _execute(self, data, parent_data, common_data): } self.is_op_cmd_configured(op_type, process_info, raise_exception=True) - proc_operate_req.append( - { + local_inst_name: str = f"{process_info['bk_process_name']}_{local_inst_id}" + + host_identity: Dict[str, Any] = { + "bk_host_innerip": host_info["bk_host_innerip"], + "bk_cloud_id": host_info["bk_cloud_id"], + "bk_agent_id": host_info.get("bk_agent_id", ""), + } + + if local_inst_name in proc_operate_req: + proc_operate_req[local_inst_name]["hosts"].append(host_identity) + else: + proc_operate_req[local_inst_name] = { "meta": { "namespace": NAMESPACE.format(bk_biz_id=process_info["bk_biz_id"]), - "name": f"{process_info['bk_process_name']}_{local_inst_id}", - "labels": { - "bk_process_name": process_info["bk_process_name"], - "bk_process_id": process_info["bk_process_id"], - }, + "name": local_inst_name, }, "op_type": op_type, - "hosts": [ - { - "bk_host_innerip": host_info["bk_host_innerip"], - "bk_cloud_id": host_info["bk_cloud_id"], - "bk_agent_id": host_info.get("bk_agent_id", ""), - } - ], + "hosts": [host_identity], "spec": { "identity": { "index_key": "", @@ -339,24 +355,34 @@ def _execute(self, data, parent_data, common_data): }, }, } - ) + # pipeline-engine会把data转为json,不能用int作为key data.outputs.proc_op_status_map[str(job_task.id)] = GseDataErrorCode.RUNNING - task_id = common_data.gse_api_helper.operate_proc_multi(proc_operate_req=proc_operate_req) + + if not proc_operate_req.values(): + self.finish_schedule() + return True + + task_id = common_data.gse_api_helper.operate_proc_multi(proc_operate_req=list(proc_operate_req.values())) data.outputs.task_id = task_id + data.outputs.no_agent_id_job_task_ids = no_agent_id_job_task_ids return self.return_data(result=True) def _schedule(self, data, parent_data, common_data, callback_data=None): job_tasks = data.get_one_of_inputs("job_tasks") op_type = data.get_one_of_inputs("op_type") task_id = data.get_one_of_outputs("task_id") + no_agent_id_job_task_ids = data.get_one_of_outputs("no_agent_id_job_task_ids", []) + gse_api_result = common_data.gse_api_helper.get_proc_operate_result(task_id) if gse_api_result["code"] == GSE_RUNNING_TASK_CODE: # 查询的任务等待执行中,还未入到redis,继续下一次查询 return self.return_data(result=True) for job_task in job_tasks: + if job_task.id in no_agent_id_job_task_ids: + continue local_inst_id = job_task.extra_data["local_inst_id"] task_result = self.get_job_task_gse_result(gse_api_result, job_task, common_data) error_code = task_result.get("error_code") @@ -366,7 +392,6 @@ def _schedule(self, data, parent_data, common_data, callback_data=None): continue data.outputs.proc_op_status_map[str(job_task.id)] = error_code - if error_code == GseDataErrorCode.SUCCESS: process_inst = ProcessInst.objects.get( bk_process_id=job_task.bk_process_id, local_inst_id=local_inst_id diff --git a/apps/gsekit/process/exceptions.py b/apps/gsekit/process/exceptions.py index 3bef7dd..08f2354 100644 --- a/apps/gsekit/process/exceptions.py +++ b/apps/gsekit/process/exceptions.py @@ -56,3 +56,9 @@ class ProcessNotMatchException(ProcessBaseException): ERROR_CODE = "006" MESSAGE = _("查询进程不匹配") MESSAGE_TPL = _("查询进程不匹配: {user_bk_process_id} vs {cc_bk_process_id}") + + +class ProcessNoAgentIDException(ProcessBaseException): + ERROR_CODE = "007" + MESSAGE = _("找不到带有agent_id的进程进行状态同步,请联系管理员") + MESSAGE_TPL = _("找不到带有agent_id的进程进行状态同步,请联系管理员") diff --git a/apps/gsekit/process/handlers/process.py b/apps/gsekit/process/handlers/process.py index 73e6820..514e6cc 100644 --- a/apps/gsekit/process/handlers/process.py +++ b/apps/gsekit/process/handlers/process.py @@ -9,13 +9,14 @@ See the License for the specific language governing permissions and limitations under the License. """ import copy +from datetime import datetime import json import operator import time from collections import defaultdict from functools import reduce from itertools import groupby -from typing import List, Dict, Union +from typing import Any, List, Dict, Union from django.db import transaction from django.db.models import Q, QuerySet @@ -43,6 +44,7 @@ from common.log import logger from apps.adapters.api.gse import get_gse_api_helper from apps.core.gray.tools import GrayTools +from env.constants import GseVersion class ProcessHandler(APIModel): @@ -512,6 +514,8 @@ def sync_biz_process(self): self.create_process_inst(process_list) + return process_list + def generate_process_inst_migrate_data(self, process_list: List) -> Dict: """计算准备好变更实例所需的数据""" @@ -947,8 +951,8 @@ def sync_proc_status_to_db(self, proc_status_infos=None, gse_api_helper: GseApiB def get_proc_inst_status_infos( proc_inst_infos, _request=None, gse_api_helper: GseApiBaseHelper = None ) -> List[Dict]: - proc_operate_req_slice = [] meta_key_uniq_key_map = {} + proc_operate_req: Dict[str, Dict[str, Any]] = {} for proc_inst_info in proc_inst_infos: host_info = proc_inst_info["host_info"] process_info = proc_inst_info["process_info"] @@ -983,26 +987,34 @@ def get_proc_inst_status_infos( meta_key: str = gse_api_helper.get_gse_proc_key( host_info, namespace=namespace, proc_name=f"{process_info['bk_process_name']}_{local_inst_id}" ) + bk_agent_id: str = host_info.get("bk_agent_id", "") + if gse_api_helper.version == GseVersion.V2.value and not bk_agent_id: + # 对于V2来说必须使用agentid进行查询 + logger.info( + f"get_proc_inst_status failed-> namespace: {namespace}, meta_key:{meta_key}, uniq_key: {uniq_key}" + ) + continue meta_key_uniq_key_map[meta_key] = uniq_key - proc_operate_req_slice.append( - { + + local_inst_name: str = f"{process_info['bk_process_name']}_{local_inst_id}" + + host_identity: Dict[str, Any] = { + "bk_host_innerip": host_info["bk_host_innerip"], + "bk_cloud_id": host_info["bk_cloud_id"], + "bk_agent_id": host_info.get("bk_agent_id", ""), + } + + if local_inst_name in proc_operate_req: + proc_operate_req[local_inst_name]["hosts"].append(host_identity) + else: + proc_operate_req[local_inst_name] = { "meta": { "namespace": namespace, - "name": f"{process_info['bk_process_name']}_{local_inst_id}", - "labels": { - "bk_process_name": process_info["bk_process_name"], - "bk_process_id": process_info["bk_process_id"], - }, + "name": local_inst_name, }, "op_type": GseOpType.CHECK, - "hosts": [ - { - "bk_host_innerip": host_info["bk_host_innerip"], - "bk_cloud_id": host_info["bk_cloud_id"], - "bk_agent_id": host_info.get("bk_agent_id", ""), - } - ], + "hosts": [host_identity], "spec": { "identity": { "index_key": "", @@ -1026,9 +1038,9 @@ def get_proc_inst_status_infos( }, }, } - ) - - gse_task_id: str = gse_api_helper.operate_proc_multi(proc_operate_req=proc_operate_req_slice) + if not proc_operate_req.values(): + raise exceptions.ProcessNoAgentIDException() + gse_task_id: str = gse_api_helper.operate_proc_multi(proc_operate_req=list(proc_operate_req.values())) proc_inst_status_infos = [] uniq_keys_recorded = set() @@ -1109,11 +1121,11 @@ def get_proc_inst_status_infos( ) return proc_inst_status_infos - def sync_biz_process_status(self): + def sync_biz_process_status(self, process_related_infos=None): begin_time = time.time() - - process_related_infos = batch_request(CCApi.list_process_related_info, {"bk_biz_id": self.bk_biz_id}) + if not process_related_infos: + process_related_infos = batch_request(CCApi.list_process_related_info, {"bk_biz_id": self.bk_biz_id}) bk_process_ids = [process_info["process"]["bk_process_id"] for process_info in process_related_infos] proc_inst_map = defaultdict(list) for proc_inst in ProcessInst.objects.filter(bk_process_id__in=bk_process_ids).values( @@ -1164,6 +1176,14 @@ def sync_biz_process_status(self): cost_time = time.time() - begin_time logger.info("[sync_proc_status] cost: {cost_time}s".format(cost_time=cost_time)) + # 记录同步时间 + with transaction.atomic(): + sync_proc_status_time, _ = GlobalSettings.objects.select_for_update().get_or_create( + key=GlobalSettings.KEYS.SYNC_PROC_STATUS_TIME, defaults={"v_json": {}} + ) + sync_proc_status_time.v_json[str(self.bk_biz_id)] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + sync_proc_status_time.save() + return {"cost_time": cost_time} def process_instance_simple( @@ -1207,3 +1227,9 @@ def process_info(self, bk_process_id): # 若有疑问请联系 CMDB 排查 raise ProcessNotMatchException(user_bk_process_id=bk_process_id, cc_bk_process_id=cc_process_id) return process_list[0] + + def sync_process_status_time(self): + sync_process_status_time: Dict[str:str] = GlobalSettings.get_config( + key=GlobalSettings.KEYS.SYNC_PROC_STATUS_TIME, default={} + ) + return {"time": sync_process_status_time.get(str(self.bk_biz_id), "")} diff --git a/apps/gsekit/process/views/process.py b/apps/gsekit/process/views/process.py index 32b9998..5804c12 100644 --- a/apps/gsekit/process/views/process.py +++ b/apps/gsekit/process/views/process.py @@ -240,3 +240,8 @@ def process_instance_simple(self, request, bk_biz_id, *args, **kwargs): expression=self.validated_data.get("expression"), ) ) + + @swagger_auto_schema(operation_summary="获取业务进程同步时间", tags=ProcessViewTags) + @action(detail=False, methods=["GET"]) + def sync_process_status_time(self, request, bk_biz_id, *args, **kwargs): + return Response(ProcessHandler(bk_biz_id=bk_biz_id).sync_process_status_time()) diff --git a/common/context_processors.py b/common/context_processors.py index de1639d..22b4089 100644 --- a/common/context_processors.py +++ b/common/context_processors.py @@ -20,8 +20,8 @@ 除setting外的其他context_processor内容,均采用组件的方式(string) """ WEB_TITLE_MAP = { - "ieod": _("{app_name} | 腾讯蓝鲸智云").format(app_name=settings.APP_NAME), - "open": _("{app_name} | 腾讯蓝鲸智云").format(app_name=settings.APP_NAME), + "ieod": _("{app_name} | 蓝鲸智云").format(app_name=settings.APP_NAME), + "open": _("{app_name} | 蓝鲸智云").format(app_name=settings.APP_NAME), } @@ -58,4 +58,7 @@ def mysetting(request): "CMDB_URL": settings.BK_CC_HOST, "TAM_AEGIS_KEY": settings.TAM_AEGIS_KEY, "TAM_AEGIS_URL": settings.TAM_AEGIS_URL, + "BKPAAS_SHARED_RES_URL": settings.BKPAAS_SHARED_RES_URL, + "BK_COMPONENT_API_URL": settings.BK_COMPONENT_API_OVERWRITE_URL, + "BK_DOMAIN": settings.BK_DOMAIN, } diff --git a/config/__init__.py b/config/__init__.py index d0143de..9ef2af7 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -18,7 +18,6 @@ # This will make sure the app is always imported when # Django starts so that shared_task will use this app. from blueapps.core.celery import celery_app -from django.utils.translation import ugettext_lazy as _ # app 基本信息 @@ -49,4 +48,4 @@ def get_env_or_raise(key): BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # GSEKit 对于外部用户来说理解成本比较高,于是通过 RUN_ENV 区分内外部应用名称 -APP_NAME = (_("进程配置管理"), _("GSEKit"))[RUN_VER == "ieod"] +APP_NAME = "GSEKit" diff --git a/config/default.py b/config/default.py index 734463f..7edaab0 100644 --- a/config/default.py +++ b/config/default.py @@ -209,6 +209,7 @@ BK_CC_HOST = os.environ.get("BK_CC_HOST", BK_PAAS_HOST.replace("paas", "cmdb")) BK_SAAS_HOST = env.BK_SAAS_HOST +BK_DOMAIN = os.environ.get("BKPAAS_BK_DOMAIN", "") BK_ADMIN_USERNAME = os.getenv("BKAPP_ADMIN_USERNAME", "admin") @@ -251,6 +252,9 @@ TAM_AEGIS_KEY = os.getenv("BKAPP_TAM_AEGIS_KEY") TAM_AEGIS_URL = os.getenv("BKAPP_TAM_AEGIS_URL") +# 平台公共信息 +BKPAAS_SHARED_RES_URL = os.getenv("BKPAAS_SHARED_RES_URL", "") + # ============================================================================== # Cache # ============================================================================== diff --git a/requirements.txt b/requirements.txt index 3a2c279..ae5f6e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,14 +4,14 @@ # sed -i 's|blueapps-open|blueapps|g' requirements.txt # 确保内外部版本统一,打包脚本的替换规则为:blueapps-open -> blueapps,不替换版本号 -blueapps[opentelemetry]==4.7.0 +blueapps[opentelemetry]==4.14.0 python-json-logger==0.1.7 requests==2.22.0 MarkupSafe==1.1.1 django-dbconn-retry==0.1.5 # django -django==3.2.4 +django==3.2.15 django-celery-beat==2.2.0 #django-celery-results==2.0.0 django-filter==2.4.0 diff --git a/web/build/webpack.prod.conf.js b/web/build/webpack.prod.conf.js index 440390e..fe94232 100644 --- a/web/build/webpack.prod.conf.js +++ b/web/build/webpack.prod.conf.js @@ -150,6 +150,11 @@ const prodConf = merge(baseConf, { // webpack4 这个属性暂时设置为 none,参见 https://github.com/jantimon/html-webpack-plugin/issues/870 chunksSortMode: 'none' }), + new HtmlWebpackPlugin({ + filename: 'login_success.html', + template: resolve(__dirname, '../login_success.html'), + inject: false, + }), new MiniCssExtractPlugin({ ignoreOrder: true, diff --git a/web/index-dev.html b/web/index-dev.html index d716e04..4dd2455 100644 --- a/web/index-dev.html +++ b/web/index-dev.html @@ -4,7 +4,7 @@ - GSEKit(DEV) + GSEKit | 蓝鲸智云
@@ -20,7 +20,9 @@ const LOGIN_URL = '' const APP_NAME = '进程配置管理' const BKAPP_DOCS_URL = 'https://bk.tencent.com/docs/document/7.0/232/30348' - + const BKPAAS_SHARED_RES_URL = '' + const BK_COMPONENT_API_URL = '' + const BK_DOMAIN = '' // BK_STATIC_URL 不能以 / 结尾 if (BK_STATIC_URL.endsWith('/')) { BK_STATIC_URL = BK_STATIC_URL.slice(0, -1) @@ -41,7 +43,10 @@ CMDB_URL: CMDB_URL, LOGIN_URL: LOGIN_URL, APP_NAME: APP_NAME, - BKAPP_DOCS_URL: BKAPP_DOCS_URL + BKAPP_DOCS_URL: BKAPP_DOCS_URL, + BKPAAS_SHARED_RES_URL: BKPAAS_SHARED_RES_URL, + BK_COMPONENT_API_URL: BK_COMPONENT_API_URL, + BK_DOMAIN: BK_DOMAIN, } })() diff --git a/web/index.html b/web/index.html index c0d8111..b01c55e 100644 --- a/web/index.html +++ b/web/index.html @@ -22,6 +22,9 @@ const LOGIN_URL = '{{LOGIN_URL}}' const APP_NAME = '{{ APP_NAME }}' // 应用名称 const BKAPP_DOCS_URL = '{{ BKAPP_DOCS_URL }}' // 文档链接 + const BKPAAS_SHARED_RES_URL = '{{ BKPAAS_SHARED_RES_URL }}' //logo,title,appname,footer配置平台路径 + const BK_COMPONENT_API_URL = '{{ BK_COMPONENT_API_URL }}' + const BK_DOMAIN = '{{ BK_DOMAIN }}' // BK_STATIC_URL 不能以 / 结尾 if (BK_STATIC_URL.endsWith('/')) { @@ -45,7 +48,10 @@ CMDB_URL: CMDB_URL, LOGIN_URL: LOGIN_URL, APP_NAME: APP_NAME, - BKAPP_DOCS_URL: BKAPP_DOCS_URL + BKAPP_DOCS_URL: BKAPP_DOCS_URL, + BKPAAS_SHARED_RES_URL: BKPAAS_SHARED_RES_URL, + BK_COMPONENT_API_URL: BK_COMPONENT_API_URL, + BK_DOMAIN: BK_DOMAIN, } })() diff --git a/web/mock/ajax/processManage.js b/web/mock/ajax/processManage.js index 846fd00..dc6557d 100644 --- a/web/mock/ajax/processManage.js +++ b/web/mock/ajax/processManage.js @@ -1156,7 +1156,7 @@ export async function response (getArgs, postArgs, req) { }, { id: "bk_cloud_name", - name: '云区域', + name: '管控区域', children: [ { name: 'cloud1', diff --git a/web/package.json b/web/package.json index f43f6e1..76b6b79 100644 --- a/web/package.json +++ b/web/package.json @@ -39,7 +39,9 @@ "dependencies": { "@blueking/bkcharts": "^2.0.10", "@blueking/login-modal": "^1.0.5", + "@blueking/platform-config": "^1.0.5", "@icon-cool/bk-icon-gsekit": "0.0.9", + "@vue/composition-api": "^1.7.2", "axios": "0.21.4", "bk-magic-vue": "2.2.14", "cookie": "0.4.0", diff --git a/web/src/App.vue b/web/src/App.vue index 5fa58ac..d6d13d5 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -16,6 +16,7 @@ import { mapState } from 'vuex'; import Header from './components/Header'; import AuthModal from '@/components/Auth/AuthModal'; import AuthPage from '@/components/Auth/AuthPage'; +import { setShortcutIcon, setDocumentTitle } from '@blueking/platform-config'; export default { name: 'App', @@ -32,8 +33,11 @@ export default { computed: { ...mapState(['mainContentLoading', 'authPage']), }, - created() { + async created() { this.$store.commit('updateAppName', window.PROJECT_CONFIG.APP_NAME); + await this.$store.dispatch('platform/getConfig'); + setDocumentTitle(this.$store.state.platform.i18n); + setShortcutIcon(this.$store.state.platform.favicon); const platform = window.navigator.platform.toLowerCase(); if (platform.indexOf('win') === 0) { document.body.style['font-family'] = 'Microsoft Yahei, PingFang SC, Helvetica, Aria'; diff --git a/web/src/api/index.js b/web/src/api/index.js index 6670bad..d9c9a2f 100644 --- a/web/src/api/index.js +++ b/web/src/api/index.js @@ -184,11 +184,14 @@ function handleReject(error, config) { if (config.globalError && error.response) { const { status, data } = error.response; const nextError = { message: error.message, response: error.response }; + // 401时只唤起登录弹窗,不弹出nextError消息提示 if (status === 401) { // 未登录, o.a 登录弹窗有问题先不做弹窗 let siteLoginUrl = window.PROJECT_CONFIG.LOGIN_URL; + // 设置login_success.html文件路径 + let successBaseUrl = window.PROJECT_CONFIG.BK_STATIC_URL; // 登录成功之后的回调地址,用于执行关闭登录窗口或刷新父窗口页面等动作 - const successUrl = `${window.location.origin}/login_success.html`; + const successUrl =`${window.location.origin}${successBaseUrl}/login_success.html`; if (!siteLoginUrl) { console.error('Login URL not configured!') return @@ -205,10 +208,11 @@ function handleReject(error, config) { showLoginModal({ loginUrl }); } else if (status === 500) { nextError.message = '系统出现异常'; + messageError(nextError.message); } else if (data && data.message) { nextError.message = data.message; + messageError(nextError.message); } - messageError(nextError.message); console.error(nextError.message); return Promise.reject(nextError); } diff --git a/web/src/bk_icon_font/demo.html b/web/src/bk_icon_font/demo.html new file mode 100644 index 0000000..b022809 --- /dev/null +++ b/web/src/bk_icon_font/demo.html @@ -0,0 +1,593 @@ + + + + + + + + + Icon Cool + + +
+

+ +

+
+
单色图标
+
彩色图标
+
+
+ +

为什么使用

+ +

如何使用

+ +
+
+ +

为什么使用

+ +

如何使用

+ +
+ +
+ + + \ No newline at end of file diff --git a/web/src/bk_icon_font/fonts/iconcool.eot b/web/src/bk_icon_font/fonts/iconcool.eot new file mode 100644 index 0000000..c6ff325 Binary files /dev/null and b/web/src/bk_icon_font/fonts/iconcool.eot differ diff --git a/web/src/bk_icon_font/fonts/iconcool.svg b/web/src/bk_icon_font/fonts/iconcool.svg new file mode 100644 index 0000000..66a164c --- /dev/null +++ b/web/src/bk_icon_font/fonts/iconcool.svg @@ -0,0 +1,158 @@ + + + + + Created by font-carrier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/bk_icon_font/fonts/iconcool.ttf b/web/src/bk_icon_font/fonts/iconcool.ttf new file mode 100644 index 0000000..0d45d3b Binary files /dev/null and b/web/src/bk_icon_font/fonts/iconcool.ttf differ diff --git a/web/src/bk_icon_font/fonts/iconcool.woff b/web/src/bk_icon_font/fonts/iconcool.woff new file mode 100644 index 0000000..e238711 Binary files /dev/null and b/web/src/bk_icon_font/fonts/iconcool.woff differ diff --git a/web/src/bk_icon_font/iconcool.js b/web/src/bk_icon_font/iconcool.js new file mode 100644 index 0000000..d8851a7 --- /dev/null +++ b/web/src/bk_icon_font/iconcool.js @@ -0,0 +1,10 @@ +!(function () { + var svgCode = '' + if (document.body) { + document.body.insertAdjacentHTML('afterbegin', svgCode) + } else { + document.addEventListener('DOMContentLoaded', function() { + document.body.insertAdjacentHTML('afterbegin', svgCode) + }) + } +})() \ No newline at end of file diff --git a/web/src/bk_icon_font/iconcool.json b/web/src/bk_icon_font/iconcool.json new file mode 100644 index 0000000..6fdf184 --- /dev/null +++ b/web/src/bk_icon_font/iconcool.json @@ -0,0 +1 @@ +{"iconName":"gsekit","icons":[{"name":"close-line","svgCode":"","codepoint":"\\e101"},{"name":"down-line","svgCode":"","codepoint":"\\e102"},{"name":"edit-fill","svgCode":"","codepoint":"\\e103"},{"name":"exit-full-screen-line","svgCode":"","codepoint":"\\e104"},{"name":"full-screen-line-line","svgCode":"","codepoint":"\\e105"},{"name":"help-document-fill","svgCode":"","codepoint":"\\e106"},{"name":"help-document-line","svgCode":"","codepoint":"\\e107"},{"name":"incomplete-line","svgCode":"","codepoint":"\\e108"},{"name":"masterplate-fill","svgCode":"","codepoint":"\\e109"},{"name":"parenet-node-line","svgCode":"","codepoint":"\\e10a"},{"name":"strategy-fill","svgCode":"","codepoint":"\\e10b"},{"name":"process-manager-fill","svgCode":"","codepoint":"\\e10c"},{"name":"status-fill","svgCode":"","codepoint":"\\e10e"},{"name":"switch-line","svgCode":"","codepoint":"\\e10d"},{"name":"swither-small","svgCode":"","codepoint":"\\e10f"},{"name":"up-line","svgCode":"","codepoint":"\\e110"},{"name":"jump-fill","svgCode":"","codepoint":"\\e111"},{"name":"filter-fill","svgCode":"","codepoint":"\\e112"},{"name":"check-line","svgCode":"","codepoint":"\\e114"},{"name":"copy","svgCode":"","codepoint":"\\e115"},{"name":"paste","svgCode":"","codepoint":"\\e116"},{"name":"updating","svgCode":"","codepoint":"\\e117"},{"name":"environment","svgCode":"","codepoint":"\\e118"},{"name":"environment-2","svgCode":"","codepoint":"\\e11a"},{"name":"cc-lock","svgCode":"","codepoint":"\\e11b"},{"name":"lock-radius","svgCode":"","codepoint":"\\e11f"},{"name":"alert","svgCode":"","codepoint":"\\e120"},{"name":"logout-fill","svgCode":"","codepoint":"\\e121"},{"name":"shouqi","svgCode":"","codepoint":"\\e124"},{"name":"zhankai","svgCode":"","codepoint":"\\e125"},{"name":"weitongbu","svgCode":"","codepoint":"\\e126"},{"name":"reduce-fill","svgCode":"","codepoint":"\\e127"},{"name":"shrink-fill","svgCode":"","codepoint":"\\e128"},{"name":"expand-fill","svgCode":"","codepoint":"\\e129"},{"name":"angle-left-line","svgCode":"","codepoint":"\\e12a"},{"name":"unfinished","svgCode":"","codepoint":"\\e12d"},{"name":"correct","svgCode":"","codepoint":"\\e12e"},{"name":"bukejian","svgCode":"","codepoint":"\\e12f"},{"name":"kejian","svgCode":"","codepoint":"\\e130"},{"name":"lang-zh-cn","svgCode":"","codepoint":"\\e131"},{"name":"lang-en","svgCode":"","codepoint":"\\e132"}]} \ No newline at end of file diff --git a/web/src/bk_icon_font/style.css b/web/src/bk_icon_font/style.css new file mode 100644 index 0000000..4e67510 --- /dev/null +++ b/web/src/bk_icon_font/style.css @@ -0,0 +1,148 @@ +@font-face { + font-family: "gsekit"; + src: url("fonts/iconcool.svg#iconcool") format("svg"), +url("fonts/iconcool.ttf") format("truetype"), +url("fonts/iconcool.woff") format("woff"), +url("fonts/iconcool.eot?#iefix") format("embedded-opentype"); + font-weight: normal; + font-style: normal; +} + +.gsekit-icon { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'gsekit' !important; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + text-align: center; + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.gsekit-icon-close-line:before { + content: "\e101"; +} +.gsekit-icon-down-line:before { + content: "\e102"; +} +.gsekit-icon-edit-fill:before { + content: "\e103"; +} +.gsekit-icon-exit-full-screen-line:before { + content: "\e104"; +} +.gsekit-icon-full-screen-line-line:before { + content: "\e105"; +} +.gsekit-icon-help-document-fill:before { + content: "\e106"; +} +.gsekit-icon-help-document-line:before { + content: "\e107"; +} +.gsekit-icon-incomplete-line:before { + content: "\e108"; +} +.gsekit-icon-masterplate-fill:before { + content: "\e109"; +} +.gsekit-icon-parenet-node-line:before { + content: "\e10a"; +} +.gsekit-icon-strategy-fill:before { + content: "\e10b"; +} +.gsekit-icon-process-manager-fill:before { + content: "\e10c"; +} +.gsekit-icon-status-fill:before { + content: "\e10e"; +} +.gsekit-icon-switch-line:before { + content: "\e10d"; +} +.gsekit-icon-swither-small:before { + content: "\e10f"; +} +.gsekit-icon-up-line:before { + content: "\e110"; +} +.gsekit-icon-jump-fill:before { + content: "\e111"; +} +.gsekit-icon-filter-fill:before { + content: "\e112"; +} +.gsekit-icon-check-line:before { + content: "\e114"; +} +.gsekit-icon-copy:before { + content: "\e115"; +} +.gsekit-icon-paste:before { + content: "\e116"; +} +.gsekit-icon-updating:before { + content: "\e117"; +} +.gsekit-icon-environment:before { + content: "\e118"; +} +.gsekit-icon-environment-2:before { + content: "\e11a"; +} +.gsekit-icon-cc-lock:before { + content: "\e11b"; +} +.gsekit-icon-lock-radius:before { + content: "\e11f"; +} +.gsekit-icon-alert:before { + content: "\e120"; +} +.gsekit-icon-logout-fill:before { + content: "\e121"; +} +.gsekit-icon-shouqi:before { + content: "\e124"; +} +.gsekit-icon-zhankai:before { + content: "\e125"; +} +.gsekit-icon-weitongbu:before { + content: "\e126"; +} +.gsekit-icon-reduce-fill:before { + content: "\e127"; +} +.gsekit-icon-shrink-fill:before { + content: "\e128"; +} +.gsekit-icon-expand-fill:before { + content: "\e129"; +} +.gsekit-icon-angle-left-line:before { + content: "\e12a"; +} +.gsekit-icon-unfinished:before { + content: "\e12d"; +} +.gsekit-icon-correct:before { + content: "\e12e"; +} +.gsekit-icon-bukejian:before { + content: "\e12f"; +} +.gsekit-icon-kejian:before { + content: "\e130"; +} +.gsekit-icon-lang-zh-cn:before { + content: "\e131"; +} +.gsekit-icon-lang-en:before { + content: "\e132"; +} diff --git a/web/src/common/use-interval.js b/web/src/common/use-interval.js new file mode 100644 index 0000000..93c270a --- /dev/null +++ b/web/src/common/use-interval.js @@ -0,0 +1,57 @@ +import { ref } from '@vue/composition-api'; + +/** + * 轮询 + * @param cb 回调 + * @param interval 轮询周期 + * @param immediate 立即执行 + */ +export default function useIntervalFn( + cb, + interval = 5000, + immediate = false, +) { + const isPending = ref(false); + const flag = ref(false); + + const timer = ref(null); + + function clear() { + if (timer.value) { + clearTimeout(timer.value); + timer.value = null; + } + } + + function stop() { + isPending.value = false; + flag.value = false; + clear(); + } + + function start(...args) { + clear(); + if (!interval) return; + + flag.value = true; + async function timerFn() { + // 上一个接口未执行完,不执行本次轮询 + if (isPending.value) return; + + isPending.value = true; + await cb(...args); + isPending.value = false; + if (flag.value) { + timer.value = setTimeout(timerFn, interval); + } + } + setTimeout(() => timerFn(), immediate ? 0 : interval); + } + + return { + isPending, + timer, + start, + stop, + }; +} diff --git a/web/src/common/util.js b/web/src/common/util.js index 42a65c3..15fedb8 100644 --- a/web/src/common/util.js +++ b/web/src/common/util.js @@ -556,3 +556,28 @@ export const copyText = (text) => { document.body.removeChild(textarea); return result; }; + +/** + * 格式化日期为: 6月23号 12:00, + * @param {Number | String} val + * @return {String} + */ +export function specifiedFormatDate(val) { + const date = new Date(timeReplace(val)); + + if (isNaN(date.getTime())) { + console.warn('无效的时间'); + return ''; + } + const months = ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"]; + + const month = months[date.getMonth()]; + const day = date.getDate() + "号"; + + // 获取小时和分钟,并确保他们都是两位数 + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + + // 返回格式化后的字符串 + return `${month}${day} ${hours}:${minutes}`; +} diff --git a/web/src/components/Auth/AuthPage.vue b/web/src/components/Auth/AuthPage.vue index 877bbb8..7f745da 100644 --- a/web/src/components/Auth/AuthPage.vue +++ b/web/src/components/Auth/AuthPage.vue @@ -35,7 +35,7 @@
-

{{ $t('接入GSEKit进程管理', [appName]) }}

+

{{ $t('接入GSEKit进程管理', [app_name]) }}

- {{ appName }} + {{ app_name }} {{ $t('帮助文档') }} @@ -77,6 +77,9 @@ export default { isEmptyPage() { return this.emptyType === 'page'; }, + app_name() { + return this.$store.state.platform.i18n.name || this.appName + }, }, methods: { handleClick(type) { diff --git a/web/src/components/Footer.vue b/web/src/components/Footer.vue index e1c8ce5..b4ed99a 100644 --- a/web/src/components/Footer.vue +++ b/web/src/components/Footer.vue @@ -1,6 +1,6 @@ @@ -18,11 +23,17 @@ export default { footerContent() { return this.$store.state.meta.footerContent; }, + contact() { + return this.$store.state.platform.i18n.footerInfoHTML + }, + copyright() { + return this.$store.state.platform.footerCopyrightContent + } }, }; - diff --git a/web/src/language/lang/en.js b/web/src/language/lang/en.js index 7022f88..d6488b0 100644 --- a/web/src/language/lang/en.js +++ b/web/src/language/lang/en.js @@ -40,7 +40,7 @@ export default { 静态文件加载失败请刷新页面重试: 'Static file loading failed, please refresh the page and try again', // 首页 首页: 'Home', - 注销: 'Logout', + 退出登录: 'Sign out', // 进程管理 进程状态: 'Process Status', 进程管理: 'Process Manage', @@ -196,7 +196,7 @@ export default { 强制停止: 'Forced to stop', 托管: 'Hosting', 取消托管: 'Cancel the hosting', - 云区域: 'Cloud area', + 管控区域: 'Cloud area', 托管状态: 'Hosting state', 固资编号: 'Fixed assets number', 配置文件数: 'The configuration file number', @@ -230,8 +230,9 @@ export default { 清除所有数据: 'Clear all data', '(*)': '(*)', 操作成功: 'Operation is successful', - '内网IP、云区域': 'Intranet IP, cloud area', + '内网IP、管控区域': 'Intranet IP, cloud area', 同步进程状态: 'Sync process status', + 状态同步: 'Status synchronized as of {0}. To get the real-time status, please click "Synchronize Process Status".', 同步CMDB进程配置: 'Sync CMDB process config', 同步成功: 'Sync successfully', 暂无其他实例: 'No other instances', diff --git a/web/src/language/lang/zh.js b/web/src/language/lang/zh.js index 7de9663..b45a578 100644 --- a/web/src/language/lang/zh.js +++ b/web/src/language/lang/zh.js @@ -40,7 +40,7 @@ export default { 静态文件加载失败请刷新页面重试: '静态文件加载失败,请刷新页面重试', // 首页 首页: '首页', - 注销: '注销', + 退出登录: '退出登录', // 进程管理 进程状态: '进程状态', 进程管理: '进程管理', @@ -196,7 +196,7 @@ export default { 强制停止: '强制停止', 托管: '托管', 取消托管: '取消托管', - 云区域: '云区域', + 管控区域: '管控区域', 托管状态: '托管状态', 固资编号: '固资编号', 配置文件数: '配置文件数', @@ -232,6 +232,7 @@ export default { 操作成功: '操作成功', '内网IP、云区域': '内网IP、云区域', 同步进程状态: '同步进程状态', + 状态同步: '状态同步于:{0},获取实时状态请点击同步进程状态', 同步CMDB进程配置: '同步CMDB进程配置', 同步成功: '同步成功', 暂无其他实例: '暂无其他实例', diff --git a/web/src/main.js b/web/src/main.js index 51f94e2..da4e72e 100644 --- a/web/src/main.js +++ b/web/src/main.js @@ -9,12 +9,14 @@ import { bus } from '@/common/bus'; import '@/common/bkmagic'; import '@/common/directives'; import '@icon-cool/bk-icon-gsekit'; +import '@/bk_icon_font/style.css'; import '@/common/svg'; import '@/common/text-tool'; import { injectCSRFTokenToHeaders } from '@/api'; import '@/mixins/emptyMixin.js'; import StatusView from '@/components/StatusView'; import TableException from '@/components/Empty/TableException'; +import VueCompositionAPI from '@vue/composition-api'; try { const id = window.PROJECT_CONFIG.TAM_AEGIS_KEY; @@ -37,6 +39,7 @@ try { Vue.config.devtools = true; Vue.component('StatusView', StatusView); Vue.component('TableException', TableException); +Vue.use(VueCompositionAPI); injectCSRFTokenToHeaders(); getUserInfo().then(() => { diff --git a/web/src/store/index.js b/web/src/store/index.js index de24873..5bbafd8 100644 --- a/web/src/store/index.js +++ b/web/src/store/index.js @@ -15,6 +15,7 @@ import job from './modules/job'; import meta from './modules/meta'; import process from './modules/process'; import home from './modules/home'; +import platform from './modules/platform-config'; import { unifyObjectStyle } from '@/common/util'; import router from '@/router'; @@ -32,6 +33,7 @@ const store = new Vuex.Store({ meta, process, home, + platform, }, // 公共 store state: { diff --git a/web/src/store/modules/platform-config.js b/web/src/store/modules/platform-config.js new file mode 100644 index 0000000..f4df977 --- /dev/null +++ b/web/src/store/modules/platform-config.js @@ -0,0 +1,65 @@ +import { getPlatformConfig } from '@blueking/platform-config'; +import logoSrc from '@/assets/images/favicon.png'; + +export default { + namespaced: true, + state: { + bkAppCode: '', // appcode + name: '', // 站点的名称,通常显示在页面左上角,也会出现在网页title中 + nameEn: '', // 站点的名称-英文 + appLogo: '', // 站点logo + favicon: '', // 站点favicon + helperText: '', + helperTextEn: '', + helperLink: '', + brandImg: '', + brandImgEn: '', + brandName: '', // 品牌名,会用于拼接在站点名称后面显示在网页title中 + favIcon: '', + brandNameEn: '', // 品牌名-英文 + footerInfo: '', // 页脚的内容,仅支持 a 的 markdown 内容格式 + footerInfoEn: '', // 页脚的内容-英文 + footerCopyright: '', // 版本信息,包含 version 变量,展示在页脚内容下方 + + footerInfoHTML: '', + footerInfoHTMLEn: '', + footerCopyrightContent: '', + version: '', + + // 需要国际化的字段,根据当前语言cookie自动匹配,页面中应该优先使用这里的字段 + i18n: { + name: '', + helperText: '...', + brandImg: '...', + brandName: '...', + footerInfoHTML: '...', + }, + }, + mutations: { + updatePlatformConfig(state,value) { + Object.assign(state, value); + } + }, + actions: { + async getConfig(context) { + const defaults = { + name: 'GSEKit', + nameEn: 'GSEKit', + appLogo: logoSrc, + brandName: '蓝鲸智云', + brandNameEn: 'Tencent BlueKing', + favicon: logoSrc, + helperLink: window.PROJECT_CONFIG.BKAPP_NAV_HELPER_URL, + helperText: window.i18n.t('联系BK助手'), + } + let config; + if (window.PROJECT_CONFIG?.BKPAAS_SHARED_RES_URL) { + const url = `${window.PROJECT_CONFIG?.BKPAAS_SHARED_RES_URL}/gsekit/base.js`; + config = await getPlatformConfig(url, defaults); + } else { + config = await getPlatformConfig(defaults); + } + context.commit('updatePlatformConfig', config); + } + } +} diff --git a/web/src/store/modules/process.js b/web/src/store/modules/process.js index 46c9fca..2e2c642 100644 --- a/web/src/store/modules/process.js +++ b/web/src/store/modules/process.js @@ -70,5 +70,10 @@ export default { const url = `api/${rootState.bizId}/process/delete_process_template/`; return http.post(url, params.data); }, + // 轮询刷新同步状态时间 + ajaxFlushSyncProcessStateTime({ rootState }) { + const url = `/api/${rootState.bizId}/process/sync_process_status_time/`; + return http.get(url); + }, }, }; diff --git a/web/src/views/ProcessManage/Status/ButtonGrounp.vue b/web/src/views/ProcessManage/Status/ButtonGrounp.vue index 7e94752..70168fe 100644 --- a/web/src/views/ProcessManage/Status/ButtonGrounp.vue +++ b/web/src/views/ProcessManage/Status/ButtonGrounp.vue @@ -71,6 +71,9 @@

+

+ {{ $t('状态同步', [time] ) }} +

import { mapState } from 'vuex'; +import useIntervalFn from '@/common/use-interval'; +import { ref } from '@vue/composition-api'; +import { specifiedFormatDate } from '@/common/util'; export default { + data() { + return { + time: ref(''), + } + }, props: { isSelected: { type: Boolean, @@ -161,6 +172,42 @@ export default { this.$emit('synchronousProcess', 'config'); this.$refs.synchronousPopover.hideHandler(); }, + // 同步状态时间 + async SyncProcessStateTime() { + const res = await this.$store.dispatch('process/ajaxFlushSyncProcessStateTime'); + if (res.result) { + this.time = specifiedFormatDate(res.data.time); + } + }, + async initPolling() { + const { start, stop } = useIntervalFn(this.SyncProcessStateTime, 10000, true); + + this.stop = stop; + + // 启动轮询 + start(); + }, + stopPolling() { + if (this.stop) { + this.stop(); + } + } + }, + created() { + // 在组件创建时启动轮询 + this.initPolling(); + }, + beforeDestroy() { + // 在组件销毁前停止轮询 + this.stopPolling(); + }, + deactivated() { + // 在组件停用时停止轮询 + this.stopPolling(); + }, + destroyed() { + // 在组件销毁时停止轮询 + this.stopPolling(); }, }; @@ -177,7 +224,9 @@ export default { .king-btn { min-width: 86px; } - + .syncProcessStateTime { + margin-right: 30px; + } .synchronous-btn { position: absolute; right: 0; diff --git a/web/src/views/ProcessManage/Status/TableContent.vue b/web/src/views/ProcessManage/Status/TableContent.vue index 43b5f88..a5c0486 100644 --- a/web/src/views/ProcessManage/Status/TableContent.vue +++ b/web/src/views/ProcessManage/Status/TableContent.vue @@ -316,7 +316,7 @@ export default { sortable: true, }, { id: 'bk_cloud_name', - label: this.$t('云区域'), + label: this.$t('管控区域'), sortable: true, // }, { // id: 'config_templates', @@ -329,7 +329,7 @@ export default { fields, setting: { fields, - selectedFields: fields.slice(0, 7), + selectedFields: fields.slice(0, 8), size: 'small', }, // 是否全选 diff --git a/web/src/views/ProcessManage/Status/index.vue b/web/src/views/ProcessManage/Status/index.vue index 28ab5d6..37ded07 100644 --- a/web/src/views/ProcessManage/Status/index.vue +++ b/web/src/views/ProcessManage/Status/index.vue @@ -30,7 +30,7 @@